@@ -24,18 +24,17 @@ matrix: |
||
24 | 24 |
- env: DOCKER_IMAGE=cantino/huginn DOCKERFILE=docker/multi-process/Dockerfile |
25 | 25 |
- env: RSPEC_TASK=spec:features |
26 | 26 |
include: |
27 |
- - rvm: 2.3.0 |
|
27 |
+ - rvm: 2.3.1 |
|
28 | 28 |
env: DATABASE_ADAPTER=mysql2 DOCKER_IMAGE=cantino/huginn-single-process DOCKERFILE=docker/single-process/Dockerfile |
29 |
- - rvm: 2.3.0 |
|
29 |
+ - rvm: 2.3.1 |
|
30 | 30 |
env: DATABASE_ADAPTER=mysql2 DOCKER_IMAGE=cantino/huginn DOCKERFILE=docker/multi-process/Dockerfile |
31 |
- - rvm: 2.3.0 |
|
31 |
+ - rvm: 2.3.1 |
|
32 | 32 |
env: RSPEC_TASK=spec:features DATABASE_ADAPTER=mysql2 |
33 |
- - rvm: 2.3.0 |
|
33 |
+ - rvm: 2.3.1 |
|
34 | 34 |
env: RSPEC_TASK=spec:features DATABASE_ADAPTER=postgresql DATABASE_USERNAME=postgres |
35 | 35 |
rvm: |
36 |
-- 2.1 |
|
37 | 36 |
- 2.2 |
38 |
-- 2.3.0 |
|
37 |
+- 2.3.1 |
|
39 | 38 |
cache: bundler |
40 | 39 |
bundler_args: --without development production |
41 | 40 |
before_install: |
@@ -2,6 +2,18 @@ |
||
2 | 2 |
|
3 | 3 |
| DateOfChange | Changes | |
4 | 4 |
|----------------|--------------------------------------------------------------------------------------------------------------| |
5 |
+| Oct 17, 2016 | Normalize URL in `to_uri` and `uri_expand` liquid filters. | |
|
6 |
+| Oct 06, 2016 | `RssAgent` is reimplemented migrating its underlying feed parser from FeedNormalizer to Feedjira. [1564](https://github.com/cantino/huginn/pull/1564) | |
|
7 |
+| Oct 05, 2016 | Migrate to Rails 5. [1688](https://github.com/cantino/huginn/pull/1688) | |
|
8 |
+| Oct 05, 2016 | Improve URL normalization in `WebsiteAgent`. [1719](https://github.com/cantino/huginn/pull/1719) | |
|
9 |
+| Oct 05, 2016 | `PushoverAgent` now treats parameter options as templates rather than default values. [1720](https://github.com/cantino/huginn/pull/1720) | |
|
10 |
+| Sep 19, 2016 | Add multipart file upload to `PostAgent`. [1690](https://github.com/cantino/huginn/pull/1690) | |
|
11 |
+| Sep 08, 2016 | Allow `TwitterUserAgent` to retry failed actions. [1645](https://github.com/cantino/huginn/pull/1645) | |
|
12 |
+| Aug 16, 2016 | `EmailDigestAgent` now relies on received events, rather in memory. [1624](https://github.com/cantino/huginn/pull/1624) | |
|
13 |
+| Aug 08, 2016 | `DataOutputAgent` now limits events after ordering. [1444](https://github.com/cantino/huginn/pull/1444) | |
|
14 |
+| Aug 05, 2016 | Add `api_key` option to `UserLocationAgent`. [1613](https://github.com/cantino/huginn/pull/1613) | |
|
15 |
+| Jul 25, 2016 | Add `LiquidOutputAgent`. [1587](https://github.com/cantino/huginn/pull/1587) | |
|
16 |
+| Jul 25, 2016 | Allow `PostAgent` headers to interpolate event data. [1606](https://github.com/cantino/huginn/pull/1606) | |
|
5 | 17 |
| Jul 25, 2016 | Remove `smtp.yml` configuration file, the SMTP configuration now needs to be done via environment variables. [1595](https://github.com/cantino/huginn/pull/1595) | |
6 | 18 |
| Jul 25, 2016 | Change `jsonpath` gem to a fork located at [https://github.com/Skarlso/jsonpathv2](https://github.com/Skarlso/jsonpathv2) [1596](https://github.com/cantino/huginn/pull/1596) | |
7 | 19 |
| Jul 20, 2016 | Add redirection information to the `HttpStatusAgent` [1590](https://github.com/cantino/huginn/pull/1590) | |
@@ -1,7 +1,7 @@ |
||
1 | 1 |
source 'https://rubygems.org' |
2 | 2 |
|
3 |
-# Ruby 2.0 is the minimum requirement |
|
4 |
-ruby ['2.0.0', RUBY_VERSION].max |
|
3 |
+# Ruby 2.2.2 is the minimum requirement |
|
4 |
+ruby ['2.2.2', RUBY_VERSION].max |
|
5 | 5 |
|
6 | 6 |
# Load vendored dotenv gem and .env file |
7 | 7 |
require File.join(File.dirname(__FILE__), 'lib/gemfile_helper.rb') |
@@ -38,7 +38,8 @@ gem 'slack-notifier', '~> 1.0.0' # SlackAgent |
||
38 | 38 |
gem 'hypdf', '~> 1.0.10' # PDFInfoAgent |
39 | 39 |
|
40 | 40 |
# Weibo Agents |
41 |
-gem 'weibo_2', github: 'cantino/weibo_2', branch: 'master' |
|
41 |
+# FIXME needs to loosen omniauth dependency |
|
42 |
+gem 'weibo_2', github: 'dsander/weibo_2', branch: 'master' |
|
42 | 43 |
|
43 | 44 |
# GoogleCalendarPublishAgent |
44 | 45 |
gem "google-api-client", require: 'google/api_client' |
@@ -46,11 +47,11 @@ gem "google-api-client", require: 'google/api_client' |
||
46 | 47 |
# Twitter Agents |
47 | 48 |
gem 'twitter', '~> 5.14.0' # Must to be loaded before cantino-twitter-stream. |
48 | 49 |
gem 'twitter-stream', github: 'cantino/twitter-stream', branch: 'huginn' |
49 |
-gem 'omniauth-twitter' |
|
50 |
+gem 'omniauth-twitter', '~> 1.2.1' |
|
50 | 51 |
|
51 | 52 |
# Tumblr Agents |
52 | 53 |
gem 'tumblr_client', github: 'tumblr/tumblr_client', branch: 'master' # '>= 0.8.5' |
53 |
-gem 'omniauth-tumblr' |
|
54 |
+gem 'omniauth-tumblr', '~> 1.2' |
|
54 | 55 |
|
55 | 56 |
# Dropbox Agents |
56 | 57 |
gem 'dropbox-api' |
@@ -71,7 +72,7 @@ gem 'aws-sdk-core', '~> 2.2.15' |
||
71 | 72 |
|
72 | 73 |
# Optional Services. |
73 | 74 |
gem 'omniauth-37signals' # BasecampAgent |
74 |
-gem 'omniauth-wunderlist', github: 'wunderlist/omniauth-wunderlist', ref: 'd0910d0396107b9302aa1bc50e74bb140990ccb8' |
|
75 |
+gem 'omniauth-wunderlist' |
|
75 | 76 |
|
76 | 77 |
# Bundler <1.5 does not recognize :x64_mingw as a valid platform name. |
77 | 78 |
# Unfortunately, it can't self-update because it errors when encountering :x64_mingw. |
@@ -80,44 +81,41 @@ unless Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('1.5.0') |
||
80 | 81 |
exit 1 |
81 | 82 |
end |
82 | 83 |
|
83 |
-gem 'protected_attributes', '~>1.0.8' # This must be loaded before some other gems, like delayed_job. |
|
84 | 84 |
gem 'ace-rails-ap', '~> 2.0.1' |
85 | 85 |
gem 'bootstrap-kaminari-views', '~> 0.0.3' |
86 | 86 |
gem 'bundler', '>= 1.5.0' |
87 |
-gem 'coffee-rails', '~> 4.1.1' |
|
87 |
+gem 'coffee-rails', '~> 4.2' |
|
88 | 88 |
gem 'daemons', '~> 1.1.9' |
89 | 89 |
gem 'delayed_job', '~> 4.1.0' |
90 |
-gem 'delayed_job_active_record', github: 'collectiveidea/delayed_job_active_record', branch: 'master' |
|
91 |
-gem 'devise', '~> 3.5.4' |
|
90 |
+gem 'delayed_job_active_record', github: 'dsander/delayed_job_active_record', branch: 'rails5' |
|
91 |
+gem 'devise','~> 4.2.0' |
|
92 | 92 |
gem 'em-http-request', '~> 1.1.2' |
93 | 93 |
gem 'faraday', '~> 0.9.0' |
94 | 94 |
gem 'faraday_middleware', github: 'lostisland/faraday_middleware', branch: 'master' # '>= 0.10.1' |
95 |
-gem 'feed-normalizer' |
|
95 |
+gem 'feedjira', '~> 2.0' |
|
96 | 96 |
gem 'font-awesome-sass', '~> 4.3.2' |
97 | 97 |
gem 'foreman', '~> 0.63.0' |
98 |
-# geokit-rails doesn't work with geokit 1.8.X but it specifies ~> 1.5 |
|
99 |
-# in its own Gemfile. |
|
100 | 98 |
gem 'geokit', '~> 1.8.4' |
101 |
-gem 'geokit-rails', '~> 2.0.1' |
|
99 |
+gem 'geokit-rails', '~> 2.2.0' |
|
102 | 100 |
gem 'httparty', '~> 0.13' |
103 | 101 |
gem 'httmultiparty', '~> 0.3.16' |
104 |
-gem 'jquery-rails', '~> 3.1.3' |
|
102 |
+gem 'jquery-rails', '~> 4.2.1' |
|
105 | 103 |
gem 'huginn_agent', '~> 0.4.0' |
106 | 104 |
gem 'json', '~> 1.8.1' |
107 |
-gem 'jsonpathv2', '~> 0.0.3' |
|
108 |
-gem 'kaminari', '~> 0.16.1' |
|
105 |
+gem 'jsonpathv2', '~> 0.0.8' |
|
106 |
+gem 'kaminari', github: "amatsuda/kaminari", branch: '0-17-stable' |
|
109 | 107 |
gem 'kramdown', '~> 1.3.3' |
110 | 108 |
gem 'liquid', '~> 3.0.3' |
109 |
+gem 'loofah', '~> 2.0' |
|
111 | 110 |
gem 'mini_magick' |
112 | 111 |
gem 'multi_xml' |
113 | 112 |
gem 'nokogiri', '1.6.8' |
114 |
-gem 'omniauth' |
|
115 |
-gem 'rails', '4.2.5.2' |
|
113 |
+gem 'omniauth', '~> 1.3.1' |
|
114 |
+gem 'rails', '~> 5.0.0.1' |
|
116 | 115 |
gem 'rufus-scheduler', '~> 3.0.8', require: false |
117 |
-gem 'sass-rails', '~> 5.0.3' |
|
116 |
+gem 'sass-rails', '~> 5.0.6' |
|
118 | 117 |
gem 'select2-rails', '~> 3.5.4' |
119 | 118 |
gem 'spectrum-rails' |
120 |
-gem 'string-scrub' # for ruby <2.1 |
|
121 | 119 |
gem 'therubyracer', '~> 0.12.2' |
122 | 120 |
gem 'typhoeus', '~> 0.6.3' |
123 | 121 |
gem 'uglifier', '~> 2.7.2' |
@@ -126,12 +124,12 @@ gem 'mixpanel_client' |
||
126 | 124 |
group :development do |
127 | 125 |
gem 'better_errors', '~> 1.1' |
128 | 126 |
gem 'binding_of_caller' |
129 |
- gem 'quiet_assets' |
|
130 | 127 |
gem 'guard', '~> 2.13.0' |
131 | 128 |
gem 'guard-livereload', '~> 2.5.1' |
132 | 129 |
gem 'guard-rspec', '~> 4.6.4' |
133 | 130 |
gem 'rack-livereload', '~> 0.3.16' |
134 |
- gem 'letter_opener_web' |
|
131 |
+ gem 'letter_opener_web', '~> 1.3.0' |
|
132 |
+ gem 'web-console' |
|
135 | 133 |
|
136 | 134 |
gem 'capistrano', '~> 3.4.0' |
137 | 135 |
gem 'capistrano-rails', '~> 1.1' |
@@ -139,7 +137,8 @@ group :development do |
||
139 | 137 |
|
140 | 138 |
if_true(ENV['SPRING']) do |
141 | 139 |
gem 'spring-commands-rspec', '~> 1.0.4' |
142 |
- gem 'spring', '~> 1.6.3' |
|
140 |
+ gem 'spring', '~> 1.7.2' |
|
141 |
+ gem 'spring-watcher-listen', '~> 2.0.0' |
|
143 | 142 |
end |
144 | 143 |
|
145 | 144 |
group :test do |
@@ -150,10 +149,11 @@ group :development do |
||
150 | 149 |
gem 'pry-rails' |
151 | 150 |
gem 'pry-byebug' |
152 | 151 |
gem 'rr' |
153 |
- gem 'rspec', '~> 3.2' |
|
152 |
+ gem 'rspec', '~> 3.5' |
|
154 | 153 |
gem 'rspec-collection_matchers', '~> 1.1.0' |
155 |
- gem 'rspec-rails', '~> 3.1' |
|
156 |
- gem 'rspec-html-matchers', '~> 0.7' |
|
154 |
+ gem 'rspec-rails', '~> 3.5.2' |
|
155 |
+ gem 'rspec-html-matchers', '~> 0.8' |
|
156 |
+ gem 'rails-controller-testing' |
|
157 | 157 |
gem 'shoulda-matchers' |
158 | 158 |
gem 'vcr' |
159 | 159 |
gem 'webmock', '~> 1.17.4', require: false |
@@ -162,15 +162,17 @@ group :development do |
||
162 | 162 |
end |
163 | 163 |
|
164 | 164 |
group :production do |
165 |
- gem 'rack', '> 1.5.0' |
|
166 |
- gem 'unicorn', '~> 4.9.0' |
|
165 |
+ gem 'unicorn', '~> 5.1.0' |
|
167 | 166 |
end |
168 | 167 |
|
169 | 168 |
# Platform requirements. |
169 |
+require 'rbconfig' |
|
170 | 170 |
gem 'ffi', '>= 1.9.4' # required by typhoeus; 1.9.4 has fixes for *BSD. |
171 | 171 |
gem 'tzinfo', '>= 1.2.0' # required by rails; 1.2.0 has support for *BSD and Solaris. |
172 | 172 |
# Windows does not have zoneinfo files, so bundle the tzinfo-data gem. |
173 | 173 |
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw] |
174 |
+# BSD systems require rb-kqueue for "listen" to avoid polling for changes. |
|
175 |
+gem 'rb-kqueue', '>= 0.2', require: /bsd|dragonfly/i === RbConfig::CONFIG['target_os'] |
|
174 | 176 |
|
175 | 177 |
|
176 | 178 |
on_heroku = ENV['ON_HEROKU'] || |
@@ -185,10 +187,6 @@ ENV['DATABASE_ADAPTER'] ||= |
||
185 | 187 |
'mysql2' |
186 | 188 |
end |
187 | 189 |
|
188 |
-if_true(on_heroku) do |
|
189 |
- gem 'rails_12factor', group: :production |
|
190 |
-end |
|
191 |
- |
|
192 | 190 |
if_true(ENV['DATABASE_ADAPTER'].strip == 'postgresql') do |
193 | 191 |
gem 'pg', '~> 0.18.3' |
194 | 192 |
end |
@@ -1,4 +1,13 @@ |
||
1 | 1 |
GIT |
2 |
+ remote: git://github.com/amatsuda/kaminari.git |
|
3 |
+ revision: abbf93d557208ee1d0b612c612cd079f86ed54f4 |
|
4 |
+ branch: 0-17-stable |
|
5 |
+ specs: |
|
6 |
+ kaminari (0.17.0) |
|
7 |
+ actionpack (>= 3.0.0) |
|
8 |
+ activesupport (>= 3.0.0) |
|
9 |
+ |
|
10 |
+GIT |
|
2 | 11 |
remote: git://github.com/cantino/twitter-stream.git |
3 | 12 |
revision: f7e7edb0bae013bffabf3598e7147773d9fd370f |
4 | 13 |
branch: huginn |
@@ -9,24 +18,24 @@ GIT |
||
9 | 18 |
simple_oauth (~> 0.3.0) |
10 | 19 |
|
11 | 20 |
GIT |
12 |
- remote: git://github.com/cantino/weibo_2.git |
|
13 |
- revision: 00e57d29d8252126014b038cd738b02e05e4cfc5 |
|
14 |
- branch: master |
|
21 |
+ remote: git://github.com/dsander/delayed_job_active_record.git |
|
22 |
+ revision: b314972ccc92e0e8b03b1589174d8fb9a82b3cd0 |
|
23 |
+ branch: rails5 |
|
15 | 24 |
specs: |
16 |
- weibo_2 (0.1.7) |
|
17 |
- hashie (~> 2.0.4) |
|
18 |
- multi_json (~> 1) |
|
19 |
- oauth2 (~> 0.9.1) |
|
20 |
- rest-client (~> 1.8) |
|
25 |
+ delayed_job_active_record (4.1.1) |
|
26 |
+ activerecord (>= 3.0, < 5.1) |
|
27 |
+ delayed_job (>= 3.0, < 5) |
|
21 | 28 |
|
22 | 29 |
GIT |
23 |
- remote: git://github.com/collectiveidea/delayed_job_active_record.git |
|
24 |
- revision: 61e688e03b2ef4004b08de6d1e0a123fda8fffad |
|
30 |
+ remote: git://github.com/dsander/weibo_2.git |
|
31 |
+ revision: e5b77f21a7e9a666b582c48e16b1e96fca198cf8 |
|
25 | 32 |
branch: master |
26 | 33 |
specs: |
27 |
- delayed_job_active_record (4.1.0) |
|
28 |
- activerecord (>= 3.0, < 5.1) |
|
29 |
- delayed_job (>= 3.0, < 5) |
|
34 |
+ weibo_2 (0.1.7) |
|
35 |
+ hashie (~> 3) |
|
36 |
+ multi_json (~> 1) |
|
37 |
+ oauth2 (~> 1) |
|
38 |
+ rest-client (~> 1.8) |
|
30 | 39 |
|
31 | 40 |
GIT |
32 | 41 |
remote: git://github.com/lostisland/faraday_middleware.git |
@@ -49,15 +58,6 @@ GIT |
||
49 | 58 |
oauth |
50 | 59 |
simple_oauth |
51 | 60 |
|
52 |
-GIT |
|
53 |
- remote: git://github.com/wunderlist/omniauth-wunderlist.git |
|
54 |
- revision: d0910d0396107b9302aa1bc50e74bb140990ccb8 |
|
55 |
- ref: d0910d0396107b9302aa1bc50e74bb140990ccb8 |
|
56 |
- specs: |
|
57 |
- omniauth-wunderlist (0.0.1) |
|
58 |
- omniauth (~> 1.0) |
|
59 |
- omniauth-oauth2 (~> 1.1) |
|
60 |
- |
|
61 | 61 |
PATH |
62 | 62 |
remote: vendor/gems/dotenv-2.0.1 |
63 | 63 |
specs: |
@@ -69,50 +69,52 @@ GEM |
||
69 | 69 |
remote: https://rubygems.org/ |
70 | 70 |
specs: |
71 | 71 |
ace-rails-ap (2.0.1) |
72 |
- actionmailer (4.2.5.2) |
|
73 |
- actionpack (= 4.2.5.2) |
|
74 |
- actionview (= 4.2.5.2) |
|
75 |
- activejob (= 4.2.5.2) |
|
72 |
+ actioncable (5.0.0.1) |
|
73 |
+ actionpack (= 5.0.0.1) |
|
74 |
+ nio4r (~> 1.2) |
|
75 |
+ websocket-driver (~> 0.6.1) |
|
76 |
+ actionmailer (5.0.0.1) |
|
77 |
+ actionpack (= 5.0.0.1) |
|
78 |
+ actionview (= 5.0.0.1) |
|
79 |
+ activejob (= 5.0.0.1) |
|
76 | 80 |
mail (~> 2.5, >= 2.5.4) |
77 |
- rails-dom-testing (~> 1.0, >= 1.0.5) |
|
78 |
- actionpack (4.2.5.2) |
|
79 |
- actionview (= 4.2.5.2) |
|
80 |
- activesupport (= 4.2.5.2) |
|
81 |
- rack (~> 1.6) |
|
82 |
- rack-test (~> 0.6.2) |
|
83 |
- rails-dom-testing (~> 1.0, >= 1.0.5) |
|
81 |
+ rails-dom-testing (~> 2.0) |
|
82 |
+ actionpack (5.0.0.1) |
|
83 |
+ actionview (= 5.0.0.1) |
|
84 |
+ activesupport (= 5.0.0.1) |
|
85 |
+ rack (~> 2.0) |
|
86 |
+ rack-test (~> 0.6.3) |
|
87 |
+ rails-dom-testing (~> 2.0) |
|
84 | 88 |
rails-html-sanitizer (~> 1.0, >= 1.0.2) |
85 |
- actionview (4.2.5.2) |
|
86 |
- activesupport (= 4.2.5.2) |
|
89 |
+ actionview (5.0.0.1) |
|
90 |
+ activesupport (= 5.0.0.1) |
|
87 | 91 |
builder (~> 3.1) |
88 | 92 |
erubis (~> 2.7.0) |
89 |
- rails-dom-testing (~> 1.0, >= 1.0.5) |
|
93 |
+ rails-dom-testing (~> 2.0) |
|
90 | 94 |
rails-html-sanitizer (~> 1.0, >= 1.0.2) |
91 |
- activejob (4.2.5.2) |
|
92 |
- activesupport (= 4.2.5.2) |
|
93 |
- globalid (>= 0.3.0) |
|
94 |
- activemodel (4.2.5.2) |
|
95 |
- activesupport (= 4.2.5.2) |
|
96 |
- builder (~> 3.1) |
|
97 |
- activerecord (4.2.5.2) |
|
98 |
- activemodel (= 4.2.5.2) |
|
99 |
- activesupport (= 4.2.5.2) |
|
100 |
- arel (~> 6.0) |
|
101 |
- activesupport (4.2.5.2) |
|
95 |
+ activejob (5.0.0.1) |
|
96 |
+ activesupport (= 5.0.0.1) |
|
97 |
+ globalid (>= 0.3.6) |
|
98 |
+ activemodel (5.0.0.1) |
|
99 |
+ activesupport (= 5.0.0.1) |
|
100 |
+ activerecord (5.0.0.1) |
|
101 |
+ activemodel (= 5.0.0.1) |
|
102 |
+ activesupport (= 5.0.0.1) |
|
103 |
+ arel (~> 7.0) |
|
104 |
+ activesupport (5.0.0.1) |
|
105 |
+ concurrent-ruby (~> 1.0, >= 1.0.2) |
|
102 | 106 |
i18n (~> 0.7) |
103 |
- json (~> 1.7, >= 1.7.7) |
|
104 | 107 |
minitest (~> 5.1) |
105 |
- thread_safe (~> 0.3, >= 0.3.4) |
|
106 | 108 |
tzinfo (~> 1.1) |
107 | 109 |
addressable (2.3.8) |
108 |
- arel (6.0.3) |
|
110 |
+ arel (7.1.1) |
|
109 | 111 |
autoparse (0.3.3) |
110 | 112 |
addressable (>= 2.3.1) |
111 | 113 |
extlib (>= 0.9.15) |
112 | 114 |
multi_json (>= 1.0.0) |
113 | 115 |
aws-sdk-core (2.2.15) |
114 | 116 |
jmespath (~> 1.0) |
115 |
- bcrypt (3.1.10) |
|
117 |
+ bcrypt (3.1.11) |
|
116 | 118 |
better_errors (1.1.0) |
117 | 119 |
coderay (>= 1.0.0) |
118 | 120 |
erubis (>= 2.6.6) |
@@ -147,15 +149,15 @@ GEM |
||
147 | 149 |
chronic (0.10.2) |
148 | 150 |
cliver (0.3.2) |
149 | 151 |
coderay (1.1.0) |
150 |
- coffee-rails (4.1.1) |
|
152 |
+ coffee-rails (4.2.1) |
|
151 | 153 |
coffee-script (>= 2.2.0) |
152 |
- railties (>= 4.0.0, < 5.1.x) |
|
154 |
+ railties (>= 4.0.0, < 5.2.x) |
|
153 | 155 |
coffee-script (2.4.1) |
154 | 156 |
coffee-script-source |
155 | 157 |
execjs |
156 | 158 |
coffee-script-source (1.10.0) |
157 | 159 |
colorize (0.7.7) |
158 |
- concurrent-ruby (1.0.1) |
|
160 |
+ concurrent-ruby (1.0.2) |
|
159 | 161 |
cookiejar (0.3.2) |
160 | 162 |
coveralls (0.7.12) |
161 | 163 |
multi_json (~> 1.10) |
@@ -168,16 +170,15 @@ GEM |
||
168 | 170 |
daemons (1.1.9) |
169 | 171 |
database_cleaner (1.5.3) |
170 | 172 |
debug_inspector (0.0.2) |
171 |
- delayed_job (4.1.1) |
|
172 |
- activesupport (>= 3.0, < 5.0) |
|
173 |
+ delayed_job (4.1.2) |
|
174 |
+ activesupport (>= 3.0, < 5.1) |
|
173 | 175 |
delorean (2.1.0) |
174 | 176 |
chronic |
175 |
- devise (3.5.4) |
|
177 |
+ devise (4.2.0) |
|
176 | 178 |
bcrypt (~> 3.0) |
177 | 179 |
orm_adapter (~> 0.1) |
178 |
- railties (>= 3.2.6, < 5) |
|
180 |
+ railties (>= 4.1.0, < 5.1) |
|
179 | 181 |
responders |
180 |
- thread_safe (~> 0.1) |
|
181 | 182 |
warden (~> 1.2.3) |
182 | 183 |
diff-lcs (1.2.5) |
183 | 184 |
docile (1.1.5) |
@@ -211,15 +212,17 @@ GEM |
||
211 | 212 |
oauth (>= 0.4.1) |
212 | 213 |
execjs (2.6.0) |
213 | 214 |
extlib (0.9.16) |
214 |
- faraday (0.9.1) |
|
215 |
+ faraday (0.9.2) |
|
215 | 216 |
multipart-post (>= 1.2, < 3) |
216 |
- feed-normalizer (1.5.2) |
|
217 |
- hpricot (>= 0.6) |
|
218 |
- simple-rss (>= 1.1) |
|
217 |
+ feedjira (2.0.0) |
|
218 |
+ faraday (~> 0.9) |
|
219 |
+ faraday_middleware (~> 0.9) |
|
220 |
+ loofah (~> 2.0) |
|
221 |
+ sax-machine (~> 1.0) |
|
219 | 222 |
ffi (1.9.10) |
220 | 223 |
font-awesome-sass (4.3.2.1) |
221 | 224 |
sass (~> 3.2) |
222 |
- forecast_io (2.0.0) |
|
225 |
+ forecast_io (2.0.1) |
|
223 | 226 |
faraday |
224 | 227 |
hashie |
225 | 228 |
multi_json |
@@ -229,10 +232,10 @@ GEM |
||
229 | 232 |
formatador (0.2.5) |
230 | 233 |
geokit (1.8.5) |
231 | 234 |
multi_json (>= 1.3.2) |
232 |
- geokit-rails (2.0.1) |
|
235 |
+ geokit-rails (2.2.0) |
|
233 | 236 |
geokit (~> 1.5) |
234 | 237 |
rails (>= 3.0) |
235 |
- globalid (0.3.6) |
|
238 |
+ globalid (0.3.7) |
|
236 | 239 |
activesupport (>= 4.1.0) |
237 | 240 |
google-api-client (0.7.1) |
238 | 241 |
addressable (>= 2.3.2) |
@@ -260,15 +263,14 @@ GEM |
||
260 | 263 |
guard (~> 2.8) |
261 | 264 |
guard-compat (~> 1.0) |
262 | 265 |
multi_json (~> 1.8) |
263 |
- guard-rspec (4.6.4) |
|
266 |
+ guard-rspec (4.6.5) |
|
264 | 267 |
guard (~> 2.1) |
265 | 268 |
guard-compat (~> 1.1) |
266 | 269 |
rspec (>= 2.99.0, < 4.0) |
267 |
- hashie (2.0.5) |
|
270 |
+ hashie (3.4.6) |
|
268 | 271 |
haversine (0.3.0) |
269 | 272 |
hipchat (1.2.0) |
270 | 273 |
httparty |
271 |
- hpricot (0.8.6) |
|
272 | 274 |
httmultiparty (0.3.16) |
273 | 275 |
httparty (>= 0.7.3) |
274 | 276 |
mimemagic |
@@ -288,17 +290,15 @@ GEM |
||
288 | 290 |
httparty (~> 0.13) |
289 | 291 |
i18n (0.7.0) |
290 | 292 |
jmespath (1.1.3) |
291 |
- jquery-rails (3.1.3) |
|
292 |
- railties (>= 3.0, < 5.0) |
|
293 |
+ jquery-rails (4.2.1) |
|
294 |
+ rails-dom-testing (>= 1, < 3) |
|
295 |
+ railties (>= 4.2.0) |
|
293 | 296 |
thor (>= 0.14, < 2.0) |
294 | 297 |
json (1.8.3) |
295 |
- jsonpathv2 (0.0.3) |
|
298 |
+ jsonpathv2 (0.0.8) |
|
296 | 299 |
multi_json |
297 | 300 |
jwt (1.4.1) |
298 |
- kaminari (0.16.1) |
|
299 |
- actionpack (>= 3.0.0) |
|
300 |
- activesupport (>= 3.0.0) |
|
301 |
- kgio (2.9.3) |
|
301 |
+ kgio (2.10.0) |
|
302 | 302 |
kramdown (1.3.3) |
303 | 303 |
launchy (2.4.2) |
304 | 304 |
addressable (~> 2.3) |
@@ -308,7 +308,7 @@ GEM |
||
308 | 308 |
actionmailer (>= 3.2) |
309 | 309 |
letter_opener (~> 1.0) |
310 | 310 |
railties (>= 3.2) |
311 |
- libv8 (3.16.14.13) |
|
311 |
+ libv8 (3.16.14.15) |
|
312 | 312 |
liquid (3.0.6) |
313 | 313 |
listen (3.0.5) |
314 | 314 |
rb-fsevent (>= 0.9.3) |
@@ -318,20 +318,24 @@ GEM |
||
318 | 318 |
lumberjack (1.0.10) |
319 | 319 |
macaddr (1.7.1) |
320 | 320 |
systemu (~> 2.6.2) |
321 |
- mail (2.6.3) |
|
322 |
- mime-types (>= 1.16, < 3) |
|
321 |
+ mail (2.6.4) |
|
322 |
+ mime-types (>= 1.16, < 4) |
|
323 | 323 |
memoizable (0.4.2) |
324 | 324 |
thread_safe (~> 0.3, >= 0.3.1) |
325 | 325 |
method_source (0.8.2) |
326 |
- mime-types (2.99.1) |
|
326 |
+ mime-types (2.99.3) |
|
327 | 327 |
mimemagic (0.3.1) |
328 | 328 |
mini_magick (4.2.3) |
329 | 329 |
mini_portile2 (2.1.0) |
330 |
+<<<<<<< HEAD |
|
330 | 331 |
minitest (5.8.4) |
331 | 332 |
mixpanel_client (4.1.4) |
332 | 333 |
typhoeus |
334 |
+======= |
|
335 |
+ minitest (5.9.0) |
|
336 |
+>>>>>>> 0fcd8e285ebe9c04fb6ce5abd5a1f776021bdf89 |
|
333 | 337 |
mqtt (0.3.1) |
334 |
- multi_json (1.11.2) |
|
338 |
+ multi_json (1.12.1) |
|
335 | 339 |
multi_xml (0.5.5) |
336 | 340 |
multipart-post (2.0.0) |
337 | 341 |
mysql2 (0.3.20) |
@@ -340,8 +344,9 @@ GEM |
||
340 | 344 |
net-ftp-list (3.2.8) |
341 | 345 |
net-scp (1.2.1) |
342 | 346 |
net-ssh (>= 2.6.5) |
343 |
- net-ssh (2.9.2) |
|
347 |
+ net-ssh (3.0.2) |
|
344 | 348 |
netrc (0.10.3) |
349 |
+ nio4r (1.2.1) |
|
345 | 350 |
nokogiri (1.6.8) |
346 | 351 |
mini_portile2 (~> 2.1.0) |
347 | 352 |
pkg-config (~> 1.1.7) |
@@ -349,15 +354,15 @@ GEM |
||
349 | 354 |
nenv (~> 0.1) |
350 | 355 |
shellany (~> 0.0) |
351 | 356 |
oauth (0.4.7) |
352 |
- oauth2 (0.9.4) |
|
357 |
+ oauth2 (1.2.0) |
|
353 | 358 |
faraday (>= 0.8, < 0.10) |
354 | 359 |
jwt (~> 1.0) |
355 | 360 |
multi_json (~> 1.3) |
356 | 361 |
multi_xml (~> 0.5) |
357 |
- rack (~> 1.2) |
|
358 |
- omniauth (1.2.2) |
|
362 |
+ rack (>= 1.2, < 3) |
|
363 |
+ omniauth (1.3.1) |
|
359 | 364 |
hashie (>= 1.2, < 4) |
360 |
- rack (~> 1.0) |
|
365 |
+ rack (>= 1.0, < 3) |
|
361 | 366 |
omniauth-37signals (1.0.5) |
362 | 367 |
omniauth (~> 1.0) |
363 | 368 |
omniauth-oauth2 (~> 1.0) |
@@ -367,19 +372,21 @@ GEM |
||
367 | 372 |
evernote-thrift |
368 | 373 |
multi_json (~> 1.0) |
369 | 374 |
omniauth-oauth (~> 1.0) |
370 |
- omniauth-oauth (1.0.1) |
|
375 |
+ omniauth-oauth (1.1.0) |
|
371 | 376 |
oauth |
372 | 377 |
omniauth (~> 1.0) |
373 |
- omniauth-oauth2 (1.1.2) |
|
374 |
- faraday (>= 0.8, < 0.10) |
|
375 |
- multi_json (~> 1.3) |
|
376 |
- oauth2 (~> 0.9.3) |
|
378 |
+ omniauth-oauth2 (1.3.1) |
|
379 |
+ oauth2 (~> 1.0) |
|
377 | 380 |
omniauth (~> 1.2) |
378 |
- omniauth-tumblr (1.1) |
|
379 |
- omniauth-oauth (~> 1.0) |
|
380 |
- omniauth-twitter (1.0.1) |
|
381 |
- multi_json (~> 1.3) |
|
381 |
+ omniauth-tumblr (1.2) |
|
382 |
+ multi_json |
|
382 | 383 |
omniauth-oauth (~> 1.0) |
384 |
+ omniauth-twitter (1.2.1) |
|
385 |
+ json (~> 1.3) |
|
386 |
+ omniauth-oauth (~> 1.1) |
|
387 |
+ omniauth-wunderlist (0.0.2) |
|
388 |
+ omniauth (~> 1.0) |
|
389 |
+ omniauth-oauth2 (~> 1.1) |
|
383 | 390 |
orm_adapter (0.5.0) |
384 | 391 |
pg (0.18.3) |
385 | 392 |
pkg-config (1.1.7) |
@@ -389,8 +396,6 @@ GEM |
||
389 | 396 |
multi_json (~> 1.0) |
390 | 397 |
websocket-driver (>= 0.2.0) |
391 | 398 |
polyglot (0.3.5) |
392 |
- protected_attributes (1.0.8) |
|
393 |
- activemodel (>= 4.0.1, < 5.0) |
|
394 | 399 |
pry (0.10.3) |
395 | 400 |
coderay (~> 1.1.0) |
396 | 401 |
method_source (~> 0.8.1) |
@@ -400,49 +405,47 @@ GEM |
||
400 | 405 |
pry (~> 0.10) |
401 | 406 |
pry-rails (0.3.4) |
402 | 407 |
pry (>= 0.9.10) |
403 |
- quiet_assets (1.1.0) |
|
404 |
- railties (>= 3.1, < 5.0) |
|
405 |
- rack (1.6.4) |
|
408 |
+ rack (2.0.1) |
|
406 | 409 |
rack-livereload (0.3.16) |
407 | 410 |
rack |
408 | 411 |
rack-test (0.6.3) |
409 | 412 |
rack (>= 1.0) |
410 |
- rails (4.2.5.2) |
|
411 |
- actionmailer (= 4.2.5.2) |
|
412 |
- actionpack (= 4.2.5.2) |
|
413 |
- actionview (= 4.2.5.2) |
|
414 |
- activejob (= 4.2.5.2) |
|
415 |
- activemodel (= 4.2.5.2) |
|
416 |
- activerecord (= 4.2.5.2) |
|
417 |
- activesupport (= 4.2.5.2) |
|
413 |
+ rails (5.0.0.1) |
|
414 |
+ actioncable (= 5.0.0.1) |
|
415 |
+ actionmailer (= 5.0.0.1) |
|
416 |
+ actionpack (= 5.0.0.1) |
|
417 |
+ actionview (= 5.0.0.1) |
|
418 |
+ activejob (= 5.0.0.1) |
|
419 |
+ activemodel (= 5.0.0.1) |
|
420 |
+ activerecord (= 5.0.0.1) |
|
421 |
+ activesupport (= 5.0.0.1) |
|
418 | 422 |
bundler (>= 1.3.0, < 2.0) |
419 |
- railties (= 4.2.5.2) |
|
420 |
- sprockets-rails |
|
421 |
- rails-deprecated_sanitizer (1.0.3) |
|
422 |
- activesupport (>= 4.2.0.alpha) |
|
423 |
- rails-dom-testing (1.0.7) |
|
424 |
- activesupport (>= 4.2.0.beta, < 5.0) |
|
423 |
+ railties (= 5.0.0.1) |
|
424 |
+ sprockets-rails (>= 2.0.0) |
|
425 |
+ rails-controller-testing (1.0.1) |
|
426 |
+ actionpack (~> 5.x) |
|
427 |
+ actionview (~> 5.x) |
|
428 |
+ activesupport (~> 5.x) |
|
429 |
+ rails-dom-testing (2.0.1) |
|
430 |
+ activesupport (>= 4.2.0, < 6.0) |
|
425 | 431 |
nokogiri (~> 1.6.0) |
426 |
- rails-deprecated_sanitizer (>= 1.0.1) |
|
427 | 432 |
rails-html-sanitizer (1.0.3) |
428 | 433 |
loofah (~> 2.0) |
429 |
- rails_12factor (0.0.3) |
|
430 |
- rails_serve_static_assets |
|
431 |
- rails_stdout_logging |
|
432 |
- rails_serve_static_assets (0.0.4) |
|
433 |
- rails_stdout_logging (0.0.3) |
|
434 |
- railties (4.2.5.2) |
|
435 |
- actionpack (= 4.2.5.2) |
|
436 |
- activesupport (= 4.2.5.2) |
|
434 |
+ railties (5.0.0.1) |
|
435 |
+ actionpack (= 5.0.0.1) |
|
436 |
+ activesupport (= 5.0.0.1) |
|
437 |
+ method_source |
|
437 | 438 |
rake (>= 0.8.7) |
438 | 439 |
thor (>= 0.18.1, < 2.0) |
439 |
- raindrops (0.13.0) |
|
440 |
- rake (10.5.0) |
|
440 |
+ raindrops (0.17.0) |
|
441 |
+ rake (11.2.2) |
|
441 | 442 |
rb-fsevent (0.9.7) |
442 | 443 |
rb-inotify (0.9.5) |
443 | 444 |
ffi (>= 0.5.0) |
445 |
+ rb-kqueue (0.2.4) |
|
446 |
+ ffi (>= 0.5.0) |
|
444 | 447 |
ref (2.0.0) |
445 |
- responders (2.1.1) |
|
448 |
+ responders (2.3.0) |
|
446 | 449 |
railties (>= 4.2.0, < 5.1) |
447 | 450 |
rest-client (1.8.0) |
448 | 451 |
http-cookie (>= 1.0.2, < 2.0) |
@@ -450,32 +453,32 @@ GEM |
||
450 | 453 |
netrc (~> 0.7) |
451 | 454 |
retriable (2.0.2) |
452 | 455 |
rr (1.1.2) |
453 |
- rspec (3.2.0) |
|
454 |
- rspec-core (~> 3.2.0) |
|
455 |
- rspec-expectations (~> 3.2.0) |
|
456 |
- rspec-mocks (~> 3.2.0) |
|
456 |
+ rspec (3.5.0) |
|
457 |
+ rspec-core (~> 3.5.0) |
|
458 |
+ rspec-expectations (~> 3.5.0) |
|
459 |
+ rspec-mocks (~> 3.5.0) |
|
457 | 460 |
rspec-collection_matchers (1.1.2) |
458 | 461 |
rspec-expectations (>= 2.99.0.beta1) |
459 |
- rspec-core (3.2.1) |
|
460 |
- rspec-support (~> 3.2.0) |
|
461 |
- rspec-expectations (3.2.0) |
|
462 |
+ rspec-core (3.5.2) |
|
463 |
+ rspec-support (~> 3.5.0) |
|
464 |
+ rspec-expectations (3.5.0) |
|
462 | 465 |
diff-lcs (>= 1.2.0, < 2.0) |
463 |
- rspec-support (~> 3.2.0) |
|
464 |
- rspec-html-matchers (0.7.0) |
|
466 |
+ rspec-support (~> 3.5.0) |
|
467 |
+ rspec-html-matchers (0.8.1) |
|
465 | 468 |
nokogiri (~> 1) |
466 |
- rspec (~> 3) |
|
467 |
- rspec-mocks (3.2.1) |
|
469 |
+ rspec (>= 3.0.0.a, < 4) |
|
470 |
+ rspec-mocks (3.5.0) |
|
468 | 471 |
diff-lcs (>= 1.2.0, < 2.0) |
469 |
- rspec-support (~> 3.2.0) |
|
470 |
- rspec-rails (3.2.1) |
|
471 |
- actionpack (>= 3.0, < 4.3) |
|
472 |
- activesupport (>= 3.0, < 4.3) |
|
473 |
- railties (>= 3.0, < 4.3) |
|
474 |
- rspec-core (~> 3.2.0) |
|
475 |
- rspec-expectations (~> 3.2.0) |
|
476 |
- rspec-mocks (~> 3.2.0) |
|
477 |
- rspec-support (~> 3.2.0) |
|
478 |
- rspec-support (3.2.2) |
|
472 |
+ rspec-support (~> 3.5.0) |
|
473 |
+ rspec-rails (3.5.2) |
|
474 |
+ actionpack (>= 3.0) |
|
475 |
+ activesupport (>= 3.0) |
|
476 |
+ railties (>= 3.0) |
|
477 |
+ rspec-core (~> 3.5.0) |
|
478 |
+ rspec-expectations (~> 3.5.0) |
|
479 |
+ rspec-mocks (~> 3.5.0) |
|
480 |
+ rspec-support (~> 3.5.0) |
|
481 |
+ rspec-support (3.5.0) |
|
479 | 482 |
rturk (2.12.1) |
480 | 483 |
erector |
481 | 484 |
nokogiri |
@@ -486,12 +489,13 @@ GEM |
||
486 | 489 |
tzinfo |
487 | 490 |
safe_yaml (1.0.4) |
488 | 491 |
sass (3.4.14) |
489 |
- sass-rails (5.0.3) |
|
490 |
- railties (>= 4.0.0, < 5.0) |
|
492 |
+ sass-rails (5.0.6) |
|
493 |
+ railties (>= 4.0.0, < 6) |
|
491 | 494 |
sass (~> 3.1) |
492 | 495 |
sprockets (>= 2.8, < 4.0) |
493 | 496 |
sprockets-rails (>= 2.0, < 4.0) |
494 |
- tilt (~> 1.1) |
|
497 |
+ tilt (>= 1.1, < 3) |
|
498 |
+ sax-machine (1.3.2) |
|
495 | 499 |
select2-rails (3.5.9.3) |
496 | 500 |
thor (~> 0.14) |
497 | 501 |
shellany (0.0.1) |
@@ -502,7 +506,6 @@ GEM |
||
502 | 506 |
faraday (>= 0.9.0.rc5) |
503 | 507 |
jwt (>= 0.1.5) |
504 | 508 |
multi_json (>= 1.0.0) |
505 |
- simple-rss (1.3.1) |
|
506 | 509 |
simple_oauth (0.3.1) |
507 | 510 |
simplecov (0.9.2) |
508 | 511 |
docile (~> 1.1.0) |
@@ -513,13 +516,16 @@ GEM |
||
513 | 516 |
slop (3.6.0) |
514 | 517 |
spectrum-rails (1.3.4) |
515 | 518 |
railties (>= 3.1) |
516 |
- spring (1.6.3) |
|
519 |
+ spring (1.7.2) |
|
517 | 520 |
spring-commands-rspec (1.0.4) |
518 | 521 |
spring (>= 0.9.1) |
519 |
- sprockets (3.5.2) |
|
522 |
+ spring-watcher-listen (2.0.0) |
|
523 |
+ listen (>= 2.7, < 4.0) |
|
524 |
+ spring (~> 1.2) |
|
525 |
+ sprockets (3.7.0) |
|
520 | 526 |
concurrent-ruby (~> 1.0) |
521 | 527 |
rack (> 1, < 3) |
522 |
- sprockets-rails (3.0.3) |
|
528 |
+ sprockets-rails (3.2.0) |
|
523 | 529 |
actionpack (>= 4.0) |
524 | 530 |
activesupport (>= 4.0) |
525 | 531 |
sprockets (>= 3.0.0) |
@@ -527,7 +533,6 @@ GEM |
||
527 | 533 |
colorize (>= 0.7.0) |
528 | 534 |
net-scp (>= 1.1.2) |
529 | 535 |
net-ssh (>= 2.8.0) |
530 |
- string-scrub (0.0.5) |
|
531 | 536 |
systemu (2.6.4) |
532 | 537 |
term-ansicolor (1.3.2) |
533 | 538 |
tins (~> 1.0) |
@@ -536,7 +541,7 @@ GEM |
||
536 | 541 |
ref |
537 | 542 |
thor (0.19.1) |
538 | 543 |
thread_safe (0.3.5) |
539 |
- tilt (1.4.1) |
|
544 |
+ tilt (2.0.5) |
|
540 | 545 |
tins (1.10.1) |
541 | 546 |
treetop (1.5.3) |
542 | 547 |
polyglot (~> 0.3) |
@@ -565,20 +570,24 @@ GEM |
||
565 | 570 |
unf (0.1.4) |
566 | 571 |
unf_ext |
567 | 572 |
unf_ext (0.0.7.1) |
568 |
- unicorn (4.9.0) |
|
573 |
+ unicorn (5.1.0) |
|
569 | 574 |
kgio (~> 2.6) |
570 |
- rack |
|
571 | 575 |
raindrops (~> 0.7) |
572 | 576 |
uuid (2.3.7) |
573 | 577 |
macaddr (~> 1.0) |
574 | 578 |
uuidtools (2.1.5) |
575 | 579 |
vcr (2.9.2) |
576 |
- warden (1.2.4) |
|
580 |
+ warden (1.2.6) |
|
577 | 581 |
rack (>= 1.0) |
582 |
+ web-console (3.3.1) |
|
583 |
+ actionview (>= 5.0) |
|
584 |
+ activemodel (>= 5.0) |
|
585 |
+ debug_inspector |
|
586 |
+ railties (>= 5.0) |
|
578 | 587 |
webmock (1.17.4) |
579 | 588 |
addressable (>= 2.2.7) |
580 | 589 |
crack (>= 0.3.2) |
581 |
- websocket-driver (0.6.3) |
|
590 |
+ websocket-driver (0.6.4) |
|
582 | 591 |
websocket-extensions (>= 0.1.0) |
583 | 592 |
websocket-extensions (0.1.2) |
584 | 593 |
wunderground (1.2.0) |
@@ -603,14 +612,14 @@ DEPENDENCIES |
||
603 | 612 |
capistrano-bundler (~> 1.1.4) |
604 | 613 |
capistrano-rails (~> 1.1) |
605 | 614 |
capybara-select2 |
606 |
- coffee-rails (~> 4.1.1) |
|
615 |
+ coffee-rails (~> 4.2) |
|
607 | 616 |
coveralls (~> 0.7.4) |
608 | 617 |
daemons (~> 1.1.9) |
609 | 618 |
database_cleaner (~> 1.5.3) |
610 | 619 |
delayed_job (~> 4.1.0) |
611 | 620 |
delayed_job_active_record! |
612 | 621 |
delorean |
613 |
- devise (~> 3.5.4) |
|
622 |
+ devise (~> 4.2.0) |
|
614 | 623 |
dotenv! |
615 | 624 |
dotenv-rails! |
616 | 625 |
dropbox-api |
@@ -618,13 +627,13 @@ DEPENDENCIES |
||
618 | 627 |
evernote_oauth |
619 | 628 |
faraday (~> 0.9.0) |
620 | 629 |
faraday_middleware! |
621 |
- feed-normalizer |
|
630 |
+ feedjira (~> 2.0) |
|
622 | 631 |
ffi (>= 1.9.4) |
623 | 632 |
font-awesome-sass (~> 4.3.2) |
624 | 633 |
forecast_io (~> 2.0.0) |
625 | 634 |
foreman (~> 0.63.0) |
626 | 635 |
geokit (~> 1.8.4) |
627 |
- geokit-rails (~> 2.0.1) |
|
636 |
+ geokit-rails (~> 2.2.0) |
|
628 | 637 |
google-api-client |
629 | 638 |
guard (~> 2.13.0) |
630 | 639 |
guard-livereload (~> 2.5.1) |
@@ -635,14 +644,15 @@ DEPENDENCIES |
||
635 | 644 |
httparty (~> 0.13) |
636 | 645 |
huginn_agent (~> 0.4.0) |
637 | 646 |
hypdf (~> 1.0.10) |
638 |
- jquery-rails (~> 3.1.3) |
|
647 |
+ jquery-rails (~> 4.2.1) |
|
639 | 648 |
json (~> 1.8.1) |
640 |
- jsonpathv2 (~> 0.0.3) |
|
641 |
- kaminari (~> 0.16.1) |
|
649 |
+ jsonpathv2 (~> 0.0.8) |
|
650 |
+ kaminari! |
|
642 | 651 |
kramdown (~> 1.3.3) |
643 |
- letter_opener_web |
|
652 |
+ letter_opener_web (~> 1.3.0) |
|
644 | 653 |
liquid (~> 3.0.3) |
645 | 654 |
listen (~> 3.0.5) |
655 |
+ loofah (~> 2.0) |
|
646 | 656 |
mini_magick |
647 | 657 |
mixpanel_client |
648 | 658 |
mqtt |
@@ -650,39 +660,37 @@ DEPENDENCIES |
||
650 | 660 |
mysql2 (~> 0.3.20) |
651 | 661 |
net-ftp-list (~> 3.2.8) |
652 | 662 |
nokogiri (= 1.6.8) |
653 |
- omniauth |
|
663 |
+ omniauth (~> 1.3.1) |
|
654 | 664 |
omniauth-37signals |
655 | 665 |
omniauth-dropbox |
656 | 666 |
omniauth-evernote |
657 |
- omniauth-tumblr |
|
658 |
- omniauth-twitter |
|
659 |
- omniauth-wunderlist! |
|
667 |
+ omniauth-tumblr (~> 1.2) |
|
668 |
+ omniauth-twitter (~> 1.2.1) |
|
669 |
+ omniauth-wunderlist |
|
660 | 670 |
pg (~> 0.18.3) |
661 | 671 |
poltergeist |
662 |
- protected_attributes (~> 1.0.8) |
|
663 | 672 |
pry-byebug |
664 | 673 |
pry-rails |
665 |
- quiet_assets |
|
666 |
- rack (> 1.5.0) |
|
667 | 674 |
rack-livereload (~> 0.3.16) |
668 |
- rails (= 4.2.5.2) |
|
669 |
- rails_12factor |
|
675 |
+ rails (~> 5.0.0.1) |
|
676 |
+ rails-controller-testing |
|
677 |
+ rb-kqueue (>= 0.2) |
|
670 | 678 |
rr |
671 |
- rspec (~> 3.2) |
|
679 |
+ rspec (~> 3.5) |
|
672 | 680 |
rspec-collection_matchers (~> 1.1.0) |
673 |
- rspec-html-matchers (~> 0.7) |
|
674 |
- rspec-rails (~> 3.1) |
|
681 |
+ rspec-html-matchers (~> 0.8) |
|
682 |
+ rspec-rails (~> 3.5.2) |
|
675 | 683 |
rturk (~> 2.12.1) |
676 | 684 |
ruby-growl (~> 4.1.0) |
677 | 685 |
rufus-scheduler (~> 3.0.8) |
678 |
- sass-rails (~> 5.0.3) |
|
686 |
+ sass-rails (~> 5.0.6) |
|
679 | 687 |
select2-rails (~> 3.5.4) |
680 | 688 |
shoulda-matchers |
681 | 689 |
slack-notifier (~> 1.0.0) |
682 | 690 |
spectrum-rails |
683 |
- spring (~> 1.6.3) |
|
691 |
+ spring (~> 1.7.2) |
|
684 | 692 |
spring-commands-rspec (~> 1.0.4) |
685 |
- string-scrub |
|
693 |
+ spring-watcher-listen (~> 2.0.0) |
|
686 | 694 |
therubyracer (~> 0.12.2) |
687 | 695 |
tumblr_client! |
688 | 696 |
twilio-ruby (~> 3.11.5) |
@@ -692,15 +700,23 @@ DEPENDENCIES |
||
692 | 700 |
tzinfo (>= 1.2.0) |
693 | 701 |
tzinfo-data |
694 | 702 |
uglifier (~> 2.7.2) |
695 |
- unicorn (~> 4.9.0) |
|
703 |
+ unicorn (~> 5.1.0) |
|
696 | 704 |
vcr |
705 |
+ web-console |
|
697 | 706 |
webmock (~> 1.17.4) |
698 | 707 |
weibo_2! |
699 | 708 |
wunderground (~> 1.2.0) |
700 | 709 |
xmpp4r (~> 0.5.6) |
701 | 710 |
|
702 | 711 |
RUBY VERSION |
712 |
+<<<<<<< HEAD |
|
703 | 713 |
ruby 2.0.0p648 |
704 | 714 |
|
705 | 715 |
BUNDLED WITH |
706 | 716 |
1.12.5 |
717 |
+======= |
|
718 |
+ ruby 2.3.1p112 |
|
719 |
+ |
|
720 |
+BUNDLED WITH |
|
721 |
+ 1.13.2 |
|
722 |
+>>>>>>> 0fcd8e285ebe9c04fb6ce5abd5a1f776021bdf89 |
@@ -64,7 +64,7 @@ If you just want to play around, you can simply fork this repository, then perfo |
||
64 | 64 |
* Run `git remote add upstream https://github.com/cantino/huginn.git` to add the main repository as a remote for your fork. |
65 | 65 |
* Copy `.env.example` to `.env` (`cp .env.example .env`) and edit `.env`, at least updating the `APP_SECRET_TOKEN` variable. |
66 | 66 |
* Run `bundle` to install dependencies |
67 |
-* Run `bundle exec rake db:create`, `bundle exec rake db:migrate`, and then `bundle exec rake db:seed` to create a development MySQL database with some example Agents. |
|
67 |
+* Run `bundle exec rake db:create`, `bundle exec rake db:migrate`, and then `bundle exec rake db:seed` to create a development database with some example Agents. |
|
68 | 68 |
* Run `bundle exec foreman start`, visit [http://localhost:3000/][localhost], and login with the username of `admin` and the password of `password`. |
69 | 69 |
* Setup some Agents! |
70 | 70 |
* Read the [wiki][wiki] for usage examples and to get started making new Agents. |
@@ -133,5 +133,5 @@ We assume your deployment will run over SSL. This is a very good idea! However, |
||
133 | 133 |
|
134 | 134 |
Huginn is provided under the MIT License. |
135 | 135 |
|
136 |
-[](https://travis-ci.org/cantino/huginn) [](https://coveralls.io/r/cantino/huginn) [](https://bitdeli.com/free "Bitdeli Badge") [](https://gemnasium.com/cantino/huginn) [](https://www.bountysource.com/trackers/282580-huginn?utm_source=282580&utm_medium=shield&utm_campaign=TRACKER_BADGE) |
|
136 |
+[](https://travis-ci.org/cantino/huginn) [](https://coveralls.io/r/cantino/huginn) [](https://gemnasium.com/cantino/huginn) [](https://www.bountysource.com/trackers/282580-huginn?utm_source=282580&utm_medium=shield&utm_campaign=TRACKER_BADGE) |
|
137 | 137 |
|
@@ -2,6 +2,6 @@ |
||
2 | 2 |
# Add your own tasks in files placed in lib/tasks ending in .rake, |
3 | 3 |
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. |
4 | 4 |
|
5 |
-require File.expand_path('../config/application', __FILE__) |
|
5 |
+require_relative 'config/application' |
|
6 | 6 |
|
7 | 7 |
Huginn::Application.load_tasks |
@@ -2,3 +2,7 @@ |
||
2 | 2 |
#= require ace/mode-javascript.js |
3 | 3 |
#= require ace/mode-markdown.js |
4 | 4 |
#= require ace/mode-coffee.js |
5 |
+#= require ace/mode-sql.js |
|
6 |
+#= require ace/mode-json.js |
|
7 |
+#= require ace/mode-yaml.js |
|
8 |
+#= require ace/mode-text.js |
@@ -53,10 +53,13 @@ $ -> |
||
53 | 53 |
updateDropdownData = (form_data, element, data) -> |
54 | 54 |
returnedResults[form_data.attribute] = {text: 'Options', children: data} |
55 | 55 |
$(element).trigger('change') |
56 |
+ $("input[role~=completable]").off 'select2-opening', select2OpeningCallback |
|
56 | 57 |
$(element).select2('open') |
58 |
+ $("input[role~=completable]").on 'select2-opening', select2OpeningCallback |
|
57 | 59 |
|
58 |
- $("input[role~=completable]").on 'select2-open', (e) -> |
|
60 |
+ select2OpeningCallback = (e) -> |
|
59 | 61 |
form_data = getFormData(e.currentTarget) |
62 |
+ delete returnedResults[form_data.attribute] if returnedResults[form_data.attribute] && !$(e.currentTarget).data('cacheResponse') |
|
60 | 63 |
return if returnedResults[form_data.attribute] |
61 | 64 |
|
62 | 65 |
$.ajax '/agents/complete', |
@@ -67,10 +70,12 @@ $ -> |
||
67 | 70 |
error: (data) -> |
68 | 71 |
updateDropdownData(form_data, e.currentTarget, [{id: undefined, text: 'Error loading data.'}]) |
69 | 72 |
|
73 |
+ $("input[role~=completable]").on 'select2-opening', select2OpeningCallback |
|
74 |
+ |
|
70 | 75 |
$("input[type=radio][role~=form-configurable]").change (e) -> |
71 | 76 |
input = $(e.currentTarget).parents().siblings("input[data-attribute=#{$(e.currentTarget).data('attribute')}]") |
72 | 77 |
if $(e.currentTarget).val() == 'manual' |
73 | 78 |
input.removeClass('hidden') |
74 | 79 |
else |
75 | 80 |
input.val($(e.currentTarget).val()) |
76 |
- input.addClass('hidden') |
|
81 |
+ input.addClass('hidden') |
@@ -69,7 +69,7 @@ class @Utils |
||
69 | 69 |
json = $(e.target).find('.payload-editor').val() |
70 | 70 |
json = '{}' if json == '' |
71 | 71 |
try |
72 |
- payload = JSON.parse(json) |
|
72 |
+ payload = JSON.parse(json.replace(/\\\\([n|r|t])/g, "\\$1")) |
|
73 | 73 |
throw true unless payload.constructor is Object |
74 | 74 |
if Object.keys(payload).length == 0 |
75 | 75 |
json = '' |
@@ -36,7 +36,7 @@ class @AgentEditPage |
||
36 | 36 |
@handleTypeChange(true) |
37 | 37 |
|
38 | 38 |
# Update the dropdown to match agent description as well as agent name |
39 |
- $('#agent_type').select2 |
|
39 |
+ $('select#agent_type').select2 |
|
40 | 40 |
width: 'resolve' |
41 | 41 |
formatResult: formatAgentForSelect |
42 | 42 |
escapeMarkup: (m) -> |
@@ -177,20 +177,28 @@ class @AgentEditPage |
||
177 | 177 |
buildAce: -> |
178 | 178 |
$(".ace-editor").each -> |
179 | 179 |
unless $(this).data('initialized') |
180 |
- $(this).data('initialized', true) |
|
181 |
- $source = $($(this).data('source')).hide() |
|
180 |
+ $this = $(this) |
|
181 |
+ $this.data('initialized', true) |
|
182 |
+ $source = $($this.data('source')).hide() |
|
182 | 183 |
editor = ace.edit(this) |
183 |
- $(this).data('ace-editor', editor) |
|
184 |
+ $this.data('ace-editor', editor) |
|
184 | 185 |
session = editor.getSession() |
185 | 186 |
session.setTabSize(2) |
186 | 187 |
session.setUseSoftTabs(true) |
187 | 188 |
session.setUseWrapMode(false) |
188 | 189 |
|
189 | 190 |
setSyntax = -> |
190 |
- switch $("[name='agent[options][language]']").val() |
|
191 |
- when 'JavaScript' then session.setMode("ace/mode/javascript") |
|
192 |
- when 'CoffeeScript' then session.setMode("ace/mode/coffee") |
|
193 |
- else session.setMode("ace/mode/text") |
|
191 |
+ if mode = $this.data('mode') |
|
192 |
+ session.setMode("ace/mode/" + mode) |
|
193 |
+ |
|
194 |
+ if theme = $this.data('theme') |
|
195 |
+ editor.setTheme("ace/theme/" + theme); |
|
196 |
+ |
|
197 |
+ if mode = $("[name='agent[options][language]']").val() |
|
198 |
+ switch mode |
|
199 |
+ when 'JavaScript' then session.setMode("ace/mode/javascript") |
|
200 |
+ when 'CoffeeScript' then session.setMode("ace/mode/coffee") |
|
201 |
+ else session.setMode("ace/mode/" + mode) |
|
194 | 202 |
|
195 | 203 |
$("[name='agent[options][language]']").on 'change', setSyntax |
196 | 204 |
setSyntax() |
@@ -5,13 +5,21 @@ module FileHandling |
||
5 | 5 |
{ file_pointer: { file: file, agent_id: id } } |
6 | 6 |
end |
7 | 7 |
|
8 |
+ def has_file_pointer?(event) |
|
9 |
+ event.payload['file_pointer'] && |
|
10 |
+ event.payload['file_pointer']['file'] && |
|
11 |
+ event.payload['file_pointer']['agent_id'] |
|
12 |
+ end |
|
13 |
+ |
|
8 | 14 |
def get_io(event) |
9 |
- return nil unless event.payload['file_pointer'] && |
|
10 |
- event.payload['file_pointer']['file'] && |
|
11 |
- event.payload['file_pointer']['agent_id'] |
|
15 |
+ return nil unless has_file_pointer?(event) |
|
12 | 16 |
event.user.agents.find(event.payload['file_pointer']['agent_id']).get_io(event.payload['file_pointer']['file']) |
13 | 17 |
end |
14 | 18 |
|
19 |
+ def get_upload_io(event) |
|
20 |
+ Faraday::UploadIO.new(get_io(event), MIME::Types.type_for(File.basename(event.payload['file_pointer']['file'])).first.try(:content_type)) |
|
21 |
+ end |
|
22 |
+ |
|
15 | 23 |
def emitting_file_handling_agent_description |
16 | 24 |
@emitting_file_handling_agent_description ||= |
17 | 25 |
"This agent only emits a 'file pointer', not the data inside the files, the following agents can consume the created events: `#{receiving_file_handling_agents.join('`, `')}`. Read more about the concept in the [wiki](https://github.com/cantino/huginn/wiki/How-Huginn-works-with-files)." |
@@ -32,7 +32,7 @@ module FormConfigurable |
||
32 | 32 |
options = args.extract_options!.reverse_merge(roles: [], type: :string) |
33 | 33 |
|
34 | 34 |
if args.all? { |arg| arg.is_a?(Symbol) } |
35 |
- options.assert_valid_keys([:type, :roles, :values, :ace]) |
|
35 |
+ options.assert_valid_keys([:type, :roles, :values, :ace, :cache_response]) |
|
36 | 36 |
end |
37 | 37 |
|
38 | 38 |
if options[:type] == :array && (options[:values].blank? || !options[:values].is_a?(Array)) |
@@ -18,6 +18,11 @@ module LiquidDroppable |
||
18 | 18 |
yield [name, __send__(name)] |
19 | 19 |
} |
20 | 20 |
end |
21 |
+ |
|
22 |
+ def as_json |
|
23 |
+ return {} unless defined?(self.class::METHODS) |
|
24 |
+ Hash[self.class::METHODS.map { |m| [m, send(m).as_json]}] |
|
25 |
+ end |
|
21 | 26 |
end |
22 | 27 |
|
23 | 28 |
included do |
@@ -33,12 +38,10 @@ module LiquidDroppable |
||
33 | 38 |
self.class::Drop.new(self) |
34 | 39 |
end |
35 | 40 |
|
36 |
- class MatchDataDrop < Liquid::Drop |
|
37 |
- def initialize(object) |
|
38 |
- @object = object |
|
39 |
- end |
|
41 |
+ class MatchDataDrop < Drop |
|
42 |
+ METHODS = %w[pre_match post_match names size] |
|
40 | 43 |
|
41 |
- %w[pre_match post_match names size].each { |attr| |
|
44 |
+ METHODS.each { |attr| |
|
42 | 45 |
define_method(attr) { |
43 | 46 |
@object.__send__(attr) |
44 | 47 |
} |
@@ -64,7 +67,9 @@ module LiquidDroppable |
||
64 | 67 |
require 'uri' |
65 | 68 |
|
66 | 69 |
class URIDrop < Drop |
67 |
- URI::Generic::COMPONENT.each { |attr| |
|
70 |
+ METHODS = URI::Generic::COMPONENT |
|
71 |
+ |
|
72 |
+ METHODS.each { |attr| |
|
68 | 73 |
define_method(attr) { |
69 | 74 |
@object.__send__(attr) |
70 | 75 |
} |
@@ -76,4 +81,10 @@ module LiquidDroppable |
||
76 | 81 |
URIDrop.new(self) |
77 | 82 |
end |
78 | 83 |
end |
84 |
+ |
|
85 |
+ class ::ActiveRecord::Associations::CollectionProxy |
|
86 |
+ def to_liquid |
|
87 |
+ self.to_a.to_liquid |
|
88 |
+ end |
|
89 |
+ end |
|
79 | 90 |
end |
@@ -92,7 +92,9 @@ module LiquidInterpolatable |
||
92 | 92 |
|
93 | 93 |
def interpolate_string(string, self_object = nil) |
94 | 94 |
interpolate_with(self_object) do |
95 |
- Liquid::Template.parse(string).render!(interpolation_context) |
|
95 |
+ catch :as_object do |
|
96 |
+ Liquid::Template.parse(string).render!(interpolation_context) |
|
97 |
+ end |
|
96 | 98 |
end |
97 | 99 |
end |
98 | 100 |
|
@@ -128,9 +130,9 @@ module LiquidInterpolatable |
||
128 | 130 |
# fragment. |
129 | 131 |
def to_uri(uri, base_uri = nil) |
130 | 132 |
if base_uri |
131 |
- URI(base_uri) + uri.to_s |
|
133 |
+ Utils.normalize_uri(base_uri) + Utils.normalize_uri(uri.to_s) |
|
132 | 134 |
else |
133 |
- URI(uri.to_s) |
|
135 |
+ Utils.normalize_uri(uri.to_s) |
|
134 | 136 |
end |
135 | 137 |
rescue URI::Error |
136 | 138 |
nil |
@@ -149,7 +151,7 @@ module LiquidInterpolatable |
||
149 | 151 |
else |
150 | 152 |
url = url.to_s |
151 | 153 |
begin |
152 |
- uri = URI(url) |
|
154 |
+ uri = Utils.normalize_uri(url) |
|
153 | 155 |
rescue URI::Error |
154 | 156 |
return url |
155 | 157 |
end |
@@ -170,7 +172,7 @@ module LiquidInterpolatable |
||
170 | 172 |
case response.status |
171 | 173 |
when 301, 302, 303, 307 |
172 | 174 |
if location = response['location'] |
173 |
- uri += location |
|
175 |
+ uri += Utils.normalize_uri(location) |
|
174 | 176 |
next |
175 | 177 |
end |
176 | 178 |
end |
@@ -225,6 +227,25 @@ module LiquidInterpolatable |
||
225 | 227 |
JSON.dump(input) |
226 | 228 |
end |
227 | 229 |
|
230 |
+ # Returns a Ruby object |
|
231 |
+ # |
|
232 |
+ # It can be used as a JSONPath replacement for Agents that only support Liquid: |
|
233 |
+ # |
|
234 |
+ # Event: {"something": {"nested": {"data": 1}}} |
|
235 |
+ # Liquid: {{something.nested | as_object}} |
|
236 |
+ # Returns: {"data": 1} |
|
237 |
+ # |
|
238 |
+ # Splitting up a string with Liquid filters and return the Array: |
|
239 |
+ # |
|
240 |
+ # Event: {"data": "A,B,C"}} |
|
241 |
+ # Liquid: {{data | split: ',' | as_object}} |
|
242 |
+ # Returns: ['A', 'B', 'C'] |
|
243 |
+ # |
|
244 |
+ # as_object ALWAYS has be the last filter in a Liquid expression! |
|
245 |
+ def as_object(object) |
|
246 |
+ throw :as_object, object.as_json |
|
247 |
+ end |
|
248 |
+ |
|
228 | 249 |
private |
229 | 250 |
|
230 | 251 |
def logger |
@@ -3,7 +3,6 @@ module Oauthable |
||
3 | 3 |
|
4 | 4 |
included do |base| |
5 | 5 |
@valid_oauth_providers = :all |
6 |
- attr_accessible :service_id |
|
7 | 6 |
validates_presence_of :service_id |
8 | 7 |
end |
9 | 8 |
|
@@ -5,6 +5,8 @@ module SortableEvents |
||
5 | 5 |
validate :validate_events_order |
6 | 6 |
end |
7 | 7 |
|
8 |
+ EVENTS_ORDER_KEY = 'events_order'.freeze |
|
9 |
+ |
|
8 | 10 |
def description_events_order(*args) |
9 | 11 |
self.class.description_events_order(*args) |
10 | 12 |
end |
@@ -23,9 +25,9 @@ module SortableEvents |
||
23 | 25 |
!can_order_created_events? |
24 | 26 |
end |
25 | 27 |
|
26 |
- def description_events_order(events = 'events created in each run') |
|
28 |
+ def description_events_order(events = 'events created in each run', events_order_key = EVENTS_ORDER_KEY) |
|
27 | 29 |
<<-MD.lstrip |
28 |
- To specify the order of #{events}, set `events_order` to an array of sort keys, each of which looks like either `expression` or `[expression, type, descending]`, as described as follows: |
|
30 |
+ To specify the order of #{events}, set `#{events_order_key}` to an array of sort keys, each of which looks like either `expression` or `[expression, type, descending]`, as described as follows: |
|
29 | 31 |
|
30 | 32 |
* _expression_ is a Liquid template to generate a string to be used as sort key. |
31 | 33 |
|
@@ -48,8 +50,8 @@ module SortableEvents |
||
48 | 50 |
self.class.cannot_order_created_events? |
49 | 51 |
end |
50 | 52 |
|
51 |
- def events_order |
|
52 |
- options['events_order'] |
|
53 |
+ def events_order(key = EVENTS_ORDER_KEY) |
|
54 |
+ options[key] |
|
53 | 55 |
end |
54 | 56 |
|
55 | 57 |
module AutomaticSorter |
@@ -102,8 +104,8 @@ module SortableEvents |
||
102 | 104 |
} |
103 | 105 |
EXPRESSION_TYPES = EXPRESSION_PARSER.keys.freeze |
104 | 106 |
|
105 |
- def validate_events_order |
|
106 |
- case order_by = events_order |
|
107 |
+ def validate_events_order(events_order_key = EVENTS_ORDER_KEY) |
|
108 |
+ case order_by = events_order(events_order_key) |
|
107 | 109 |
when nil |
108 | 110 |
when Array |
109 | 111 |
# Each tuple may be either [expression, type, desc] or just |
@@ -113,29 +115,29 @@ module SortableEvents |
||
113 | 115 |
when String |
114 | 116 |
# ok |
115 | 117 |
else |
116 |
- errors.add(:base, "first element of each events_order tuple must be a Liquid template") |
|
118 |
+ errors.add(:base, "first element of each #{events_order_key} tuple must be a Liquid template") |
|
117 | 119 |
break |
118 | 120 |
end |
119 | 121 |
case type |
120 | 122 |
when nil, *EXPRESSION_TYPES |
121 | 123 |
# ok |
122 | 124 |
else |
123 |
- errors.add(:base, "second element of each events_order tuple must be #{EXPRESSION_TYPES.to_sentence(last_word_connector: ' or ')}") |
|
125 |
+ errors.add(:base, "second element of each #{events_order_key} tuple must be #{EXPRESSION_TYPES.to_sentence(last_word_connector: ' or ')}") |
|
124 | 126 |
break |
125 | 127 |
end |
126 | 128 |
if !desc.nil? && boolify(desc).nil? |
127 |
- errors.add(:base, "third element of each events_order tuple must be a boolean value") |
|
129 |
+ errors.add(:base, "third element of each #{events_order_key} tuple must be a boolean value") |
|
128 | 130 |
break |
129 | 131 |
end |
130 | 132 |
end |
131 | 133 |
else |
132 |
- errors.add(:base, "events_order must be an array of arrays") |
|
134 |
+ errors.add(:base, "#{events_order_key} must be an array of arrays") |
|
133 | 135 |
end |
134 | 136 |
end |
135 | 137 |
|
136 | 138 |
# Sort given events in order specified by the "events_order" option |
137 |
- def sort_events(events) |
|
138 |
- order_by = events_order.presence or |
|
139 |
+ def sort_events(events, events_order_key = EVENTS_ORDER_KEY) |
|
140 |
+ order_by = events_order(events_order_key).presence or |
|
139 | 141 |
return events |
140 | 142 |
|
141 | 143 |
orders = order_by.map { |_, _, desc = false| boolify(desc) } |
@@ -113,6 +113,7 @@ module WebRequestConcern |
||
113 | 113 |
unless boolify(interpolated['disable_redirect_follow']) |
114 | 114 |
builder.use FaradayMiddleware::FollowRedirects |
115 | 115 |
end |
116 |
+ builder.request :multipart |
|
116 | 117 |
builder.request :url_encoded |
117 | 118 |
|
118 | 119 |
if boolify(interpolated['disable_url_encoding']) |
@@ -1,7 +1,7 @@ |
||
1 | 1 |
class Admin::UsersController < ApplicationController |
2 |
- before_action :authenticate_admin! |
|
2 |
+ before_action :authenticate_admin!, except: [:switch_back] |
|
3 | 3 |
|
4 |
- before_action :find_user, only: [:edit, :destroy, :update, :deactivate, :activate] |
|
4 |
+ before_action :find_user, only: [:edit, :destroy, :update, :deactivate, :activate, :switch_to_user] |
|
5 | 5 |
|
6 | 6 |
helper_method :resource |
7 | 7 |
|
@@ -19,10 +19,8 @@ class Admin::UsersController < ApplicationController |
||
19 | 19 |
end |
20 | 20 |
|
21 | 21 |
def create |
22 |
- admin = params[:user].delete(:admin) |
|
23 |
- @user = User.new(params[:user]) |
|
22 |
+ @user = User.new(user_params) |
|
24 | 23 |
@user.requires_no_invitation_code! |
25 |
- @user.admin = admin |
|
26 | 24 |
|
27 | 25 |
respond_to do |format| |
28 | 26 |
if @user.save |
@@ -40,10 +38,8 @@ class Admin::UsersController < ApplicationController |
||
40 | 38 |
end |
41 | 39 |
|
42 | 40 |
def update |
43 |
- admin = params[:user].delete(:admin) |
|
44 |
- params[:user].except!(:password, :password_confirmation) if params[:user][:password].blank? |
|
45 |
- @user.assign_attributes(params[:user]) |
|
46 |
- @user.admin = admin |
|
41 |
+ params[:user].extract!(:password, :password_confirmation) if params[:user][:password].blank? |
|
42 |
+ @user.assign_attributes(user_params) |
|
47 | 43 |
|
48 | 44 |
respond_to do |format| |
49 | 45 |
if @user.save |
@@ -83,8 +79,33 @@ class Admin::UsersController < ApplicationController |
||
83 | 79 |
end |
84 | 80 |
end |
85 | 81 |
|
82 |
+ # allow an admin to sign-in as any other user |
|
83 |
+ |
|
84 |
+ def switch_to_user |
|
85 |
+ if current_user != @user |
|
86 |
+ old_user = current_user |
|
87 |
+ bypass_sign_in(@user) |
|
88 |
+ session[:original_admin_user_id] = old_user.id |
|
89 |
+ end |
|
90 |
+ redirect_to agents_path |
|
91 |
+ end |
|
92 |
+ |
|
93 |
+ def switch_back |
|
94 |
+ if session[:original_admin_user_id].present? |
|
95 |
+ bypass_sign_in(User.find(session[:original_admin_user_id])) |
|
96 |
+ session.delete(:original_admin_user_id) |
|
97 |
+ else |
|
98 |
+ redirect_to(root_path, alert: 'You must be an admin acting as a different user to do that.') and return |
|
99 |
+ end |
|
100 |
+ redirect_to admin_users_path |
|
101 |
+ end |
|
102 |
+ |
|
86 | 103 |
private |
87 | 104 |
|
105 |
+ def user_params |
|
106 |
+ params.require(:user).permit(:email, :username, :password, :password_confirmation, :admin) |
|
107 |
+ end |
|
108 |
+ |
|
88 | 109 |
def find_user |
89 | 110 |
@user = User.find(params[:id]) |
90 | 111 |
end |
@@ -8,17 +8,19 @@ module Agents |
||
8 | 8 |
elsif params[:source_ids] |
9 | 9 |
Event.where(agent_id: current_user.agents.where(id: params[:source_ids]).pluck(:id)) |
10 | 10 |
.order("id DESC").limit(5) |
11 |
+ else |
|
12 |
+ [] |
|
11 | 13 |
end |
12 | 14 |
|
13 | 15 |
render layout: false |
14 | 16 |
end |
15 | 17 |
|
16 | 18 |
def create |
17 |
- attrs = params[:agent] || {} |
|
19 |
+ attrs = agent_params |
|
18 | 20 |
if agent = current_user.agents.find_by(id: params[:agent_id]) |
19 | 21 |
# POST /agents/:id/dry_run |
20 | 22 |
if attrs.present? |
21 |
- attrs.merge!(memory: agent.memory) |
|
23 |
+ attrs = attrs.merge(memory: agent.memory) |
|
22 | 24 |
type = agent.type |
23 | 25 |
agent = Agent.build_for_type(type, current_user, attrs) |
24 | 26 |
end |
@@ -160,7 +160,7 @@ class AgentsController < ApplicationController |
||
160 | 160 |
@agent = current_user.agents.find(params[:id]) |
161 | 161 |
|
162 | 162 |
respond_to do |format| |
163 |
- if @agent.update_attributes(params[:agent]) |
|
163 |
+ if @agent.update_attributes(agent_params) |
|
164 | 164 |
format.html { redirect_back "'#{@agent.name}' was successfully updated.", return: agents_path } |
165 | 165 |
format.json { render json: @agent, status: :ok, location: agent_path(@agent) } |
166 | 166 |
else |
@@ -196,9 +196,9 @@ class AgentsController < ApplicationController |
||
196 | 196 |
build_agent |
197 | 197 |
|
198 | 198 |
if @agent.validate_option(params[:attribute]) |
199 |
- render text: 'ok' |
|
199 |
+ render plain: 'ok' |
|
200 | 200 |
else |
201 |
- render text: 'error', status: 403 |
|
201 |
+ render plain: 'error', status: 403 |
|
202 | 202 |
end |
203 | 203 |
end |
204 | 204 |
|
@@ -208,6 +208,12 @@ class AgentsController < ApplicationController |
||
208 | 208 |
render json: @agent.complete_option(params[:attribute]) |
209 | 209 |
end |
210 | 210 |
|
211 |
+ def destroy_undefined |
|
212 |
+ current_user.undefined_agents.destroy_all |
|
213 |
+ |
|
214 |
+ redirect_back "All undefined Agents have been deleted." |
|
215 |
+ end |
|
216 |
+ |
|
211 | 217 |
protected |
212 | 218 |
|
213 | 219 |
# Sanitize params[:return] to prevent open redirect attacks, a common security issue. |
@@ -220,9 +226,9 @@ class AgentsController < ApplicationController |
||
220 | 226 |
end |
221 | 227 |
|
222 | 228 |
def build_agent |
223 |
- @agent = Agent.build_for_type(params[:agent].delete(:type), |
|
229 |
+ @agent = Agent.build_for_type(agent_params[:type], |
|
224 | 230 |
current_user, |
225 |
- params[:agent]) |
|
231 |
+ agent_params.except(:type)) |
|
226 | 232 |
end |
227 | 233 |
|
228 | 234 |
def initialize_presenter |
@@ -6,18 +6,22 @@ class ApplicationController < ActionController::Base |
||
6 | 6 |
|
7 | 7 |
helper :all |
8 | 8 |
|
9 |
- def redirect_back(fallback_path, *args) |
|
10 |
- redirect_to :back, *args |
|
11 |
- rescue ActionController::RedirectBackError |
|
12 |
- redirect_to fallback_path, *args |
|
9 |
+ rescue_from 'ActiveRecord::SubclassNotFound' do |
|
10 |
+ @undefined_agent_types = current_user.undefined_agent_types |
|
11 |
+ |
|
12 |
+ render template: 'application/undefined_agents' |
|
13 |
+ end |
|
14 |
+ |
|
15 |
+ def redirect_back(fallback_path, **args) |
|
16 |
+ super(fallback_location: fallback_path, **args) |
|
13 | 17 |
end |
14 | 18 |
|
15 | 19 |
protected |
16 | 20 |
|
17 | 21 |
def configure_permitted_parameters |
18 |
- devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :password, :password_confirmation, :remember_me, :invitation_code) } |
|
19 |
- devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:login, :username, :email, :password, :remember_me) } |
|
20 |
- devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:username, :email, :password, :password_confirmation, :current_password) } |
|
22 |
+ devise_parameter_sanitizer.permit(:sign_up, keys: [:username, :email, :password, :password_confirmation, :remember_me, :invitation_code]) |
|
23 |
+ devise_parameter_sanitizer.permit(:sign_in, keys: [:login, :username, :email, :password, :remember_me]) |
|
24 |
+ devise_parameter_sanitizer.permit(:account_update, keys: [:username, :email, :password, :password_confirmation, :current_password]) |
|
21 | 25 |
end |
22 | 26 |
|
23 | 27 |
def authenticate_admin! |
@@ -60,4 +64,16 @@ class ApplicationController < ActionController::Base |
||
60 | 64 |
@basecamp_agent = current_user.agents.where(type: 'Agents::BasecampAgent').first |
61 | 65 |
end |
62 | 66 |
end |
67 |
+ |
|
68 |
+ def agent_params |
|
69 |
+ return {} unless params[:agent] |
|
70 |
+ @agent_params ||= begin |
|
71 |
+ options = params[:agent].delete(:options) if params[:agent][:options].present? |
|
72 |
+ params[:agent].permit(:memory, :name, :type, :schedule, :disabled, :keep_events_for, :propagate_immediately, :drop_pending_events, :service_id, |
|
73 |
+ source_ids: [], receiver_ids: [], scenario_ids: [], controller_ids: [], control_target_ids: []).tap do |agent_params| |
|
74 |
+ agent_params[:options] = options if options |
|
75 |
+ agent_params[:options].permit! if agent_params[:options].respond_to?(:permit!) |
|
76 |
+ end |
|
77 |
+ end |
|
78 |
+ end |
|
63 | 79 |
end |
@@ -4,7 +4,7 @@ class ScenarioImportsController < ApplicationController |
||
4 | 4 |
end |
5 | 5 |
|
6 | 6 |
def create |
7 |
- @scenario_import = ScenarioImport.new(params[:scenario_import]) |
|
7 |
+ @scenario_import = ScenarioImport.new(scenario_import_params) |
|
8 | 8 |
@scenario_import.set_user(current_user) |
9 | 9 |
|
10 | 10 |
if @scenario_import.valid? && @scenario_import.import_confirmed? && @scenario_import.import |
@@ -13,4 +13,13 @@ class ScenarioImportsController < ApplicationController |
||
13 | 13 |
render action: "new" |
14 | 14 |
end |
15 | 15 |
end |
16 |
+ |
|
17 |
+ private |
|
18 |
+ |
|
19 |
+ def scenario_import_params |
|
20 |
+ merges = params[:scenario_import].delete(:merges) |
|
21 |
+ params.require(:scenario_import).permit(:url, :data, :file, :do_import) do |params| |
|
22 |
+ params[:merges] = merges |
|
23 |
+ end |
|
24 |
+ end |
|
16 | 25 |
end |
@@ -1,3 +1,5 @@ |
||
1 |
+require 'agents_exporter' |
|
2 |
+ |
|
1 | 3 |
class ScenariosController < ApplicationController |
2 | 4 |
include SortableTable |
3 | 5 |
skip_before_action :authenticate_user!, only: :export |
@@ -69,7 +71,7 @@ class ScenariosController < ApplicationController |
||
69 | 71 |
end |
70 | 72 |
|
71 | 73 |
def create |
72 |
- @scenario = current_user.scenarios.build(params[:scenario]) |
|
74 |
+ @scenario = current_user.scenarios.build(scenario_params) |
|
73 | 75 |
|
74 | 76 |
respond_to do |format| |
75 | 77 |
if @scenario.save |
@@ -86,7 +88,7 @@ class ScenariosController < ApplicationController |
||
86 | 88 |
@scenario = current_user.scenarios.find(params[:id]) |
87 | 89 |
|
88 | 90 |
respond_to do |format| |
89 |
- if @scenario.update_attributes(params[:scenario]) |
|
91 |
+ if @scenario.update_attributes(scenario_params) |
|
90 | 92 |
format.html { redirect_to @scenario, notice: 'This Scenario was successfully updated.' } |
91 | 93 |
format.json { head :no_content } |
92 | 94 |
else |
@@ -115,4 +117,11 @@ class ScenariosController < ApplicationController |
||
115 | 117 |
format.json { head :no_content } |
116 | 118 |
end |
117 | 119 |
end |
120 |
+ |
|
121 |
+ private |
|
122 |
+ |
|
123 |
+ def scenario_params |
|
124 |
+ params.require(:scenario).permit(:name, :description, :public, :source_url, |
|
125 |
+ :tag_fg_color, :tag_bg_color, :icon, agent_ids: []) |
|
126 |
+ end |
|
118 | 127 |
end |
@@ -48,7 +48,7 @@ class UserCredentialsController < ApplicationController |
||
48 | 48 |
end |
49 | 49 |
|
50 | 50 |
def create |
51 |
- @user_credential = current_user.user_credentials.build(params[:user_credential]) |
|
51 |
+ @user_credential = current_user.user_credentials.build(user_credential_params) |
|
52 | 52 |
|
53 | 53 |
respond_to do |format| |
54 | 54 |
if @user_credential.save |
@@ -65,7 +65,7 @@ class UserCredentialsController < ApplicationController |
||
65 | 65 |
@user_credential = current_user.user_credentials.find(params[:id]) |
66 | 66 |
|
67 | 67 |
respond_to do |format| |
68 |
- if @user_credential.update_attributes(params[:user_credential]) |
|
68 |
+ if @user_credential.update_attributes(user_credential_params) |
|
69 | 69 |
format.html { redirect_to user_credentials_path, notice: 'Your credential was successfully updated.' } |
70 | 70 |
format.json { head :no_content } |
71 | 71 |
else |
@@ -84,4 +84,10 @@ class UserCredentialsController < ApplicationController |
||
84 | 84 |
format.json { head :no_content } |
85 | 85 |
end |
86 | 86 |
end |
87 |
+ |
|
88 |
+ private |
|
89 |
+ |
|
90 |
+ def user_credential_params |
|
91 |
+ params.require(:user_credential).permit(:credential_name, :credential_value, :mode) |
|
92 |
+ end |
|
87 | 93 |
end |
@@ -27,17 +27,17 @@ class WebRequestsController < ApplicationController |
||
27 | 27 |
content, status, content_type = agent.trigger_web_request(request) |
28 | 28 |
|
29 | 29 |
if content.is_a?(String) |
30 |
- render :text => content, :status => status || 200, :content_type => content_type || 'text/plain' |
|
30 |
+ render plain: content, :status => status || 200, :content_type => content_type || 'text/plain' |
|
31 | 31 |
elsif content.is_a?(Hash) |
32 | 32 |
render :json => content, :status => status || 200 |
33 | 33 |
else |
34 | 34 |
head(status || 200) |
35 | 35 |
end |
36 | 36 |
else |
37 |
- render :text => "agent not found", :status => 404 |
|
37 |
+ render plain: "agent not found", :status => 404 |
|
38 | 38 |
end |
39 | 39 |
else |
40 |
- render :text => "user not found", :status => 404 |
|
40 |
+ render plain: "user not found", :status => 404 |
|
41 | 41 |
end |
42 | 42 |
end |
43 | 43 |
|
@@ -50,9 +50,9 @@ class WebRequestsController < ApplicationController |
||
50 | 50 |
agent.trigger_web_request(request) |
51 | 51 |
end |
52 | 52 |
} |
53 |
- render :text => "ok" |
|
53 |
+ render plain: "ok" |
|
54 | 54 |
else |
55 |
- render :text => "user not found", :status => :not_found |
|
55 |
+ render plain: "user not found", :status => :not_found |
|
56 | 56 |
end |
57 | 57 |
end |
58 | 58 |
end |
@@ -113,4 +113,14 @@ module ApplicationHelper |
||
113 | 113 |
|
114 | 114 |
@highlighted_ranges.any? { |range| range.cover?(id) } |
115 | 115 |
end |
116 |
+ |
|
117 |
+ def agent_type_to_human(type) |
|
118 |
+ type.gsub(/^.*::/, '').underscore.humanize.titleize |
|
119 |
+ end |
|
120 |
+ |
|
121 |
+ private |
|
122 |
+ |
|
123 |
+ def user_omniauth_authorize_path(provider) |
|
124 |
+ send "user_#{provider}_omniauth_authorize_path" |
|
125 |
+ end |
|
116 | 126 |
end |
@@ -6,14 +6,15 @@ class AgentPropagateJob < ActiveJob::Base |
||
6 | 6 |
end |
7 | 7 |
|
8 | 8 |
def self.can_enqueue? |
9 |
- if Rails.configuration.active_job.queue_adapter == :delayed_job && |
|
10 |
- Delayed::Job.where(failed_at: nil, queue: 'propagation').count > 0 |
|
11 |
- return false |
|
12 |
- elsif Rails.configuration.active_job.queue_adapter == :resque && |
|
13 |
- (Resque.size('propagation') > 0 || |
|
14 |
- Resque.workers.select { |w| w.job && w.job['queue'] && w.job['queue']['propagation'] }.count > 0) |
|
15 |
- return false |
|
9 |
+ case queue_adapter.class.name # not using class since it would load adapter dependent gems |
|
10 |
+ when 'ActiveJob::QueueAdapters::DelayedJobAdapter' |
|
11 |
+ return Delayed::Job.where(failed_at: nil, queue: 'propagation').count == 0 |
|
12 |
+ when 'ActiveJob::QueueAdapters::ResqueAdapter' |
|
13 |
+ return Resque.size('propagation') == 0 && |
|
14 |
+ Resque.workers.select { |w| w.job && w.job['queue'] && w.job['queue']['propagation'] }.count == 0 |
|
15 |
+ else |
|
16 |
+ raise NotImplementedError, "unsupported adapter: #{queue_adapter}" |
|
16 | 17 |
end |
17 |
- true |
|
18 | 18 |
end |
19 |
+ |
|
19 | 20 |
end |
@@ -24,8 +24,6 @@ class Agent < ActiveRecord::Base |
||
24 | 24 |
|
25 | 25 |
EVENT_RETENTION_SCHEDULES = [["Forever", 0], ['1 hour', 1.hour], ['6 hours', 6.hours], ["1 day", 1.day], *([2, 3, 4, 5, 7, 14, 21, 30, 45, 90, 180, 365].map {|n| ["#{n} days", n.days] })] |
26 | 26 |
|
27 |
- attr_accessible :options, :memory, :name, :type, :schedule, :controller_ids, :control_target_ids, :disabled, :source_ids, :receiver_ids, :scenario_ids, :keep_events_for, :propagate_immediately, :drop_pending_events |
|
28 |
- |
|
29 | 27 |
json_serialize :options, :memory |
30 | 28 |
|
31 | 29 |
validates_presence_of :name, :user |
@@ -46,7 +44,7 @@ class Agent < ActiveRecord::Base |
||
46 | 44 |
after_save :possibly_update_event_expirations |
47 | 45 |
|
48 | 46 |
belongs_to :user, :inverse_of => :agents |
49 |
- belongs_to :service, :inverse_of => :agents |
|
47 |
+ belongs_to :service, :inverse_of => :agents, optional: true |
|
50 | 48 |
has_many :events, -> { order("events.id desc") }, :dependent => :delete_all, :inverse_of => :agent |
51 | 49 |
has_one :most_recent_event, -> { order("events.id desc") }, :inverse_of => :agent, :class_name => "Event" |
52 | 50 |
has_many :logs, -> { order("agent_logs.id desc") }, :dependent => :delete_all, :inverse_of => :agent, :class_name => "AgentLog" |
@@ -445,7 +443,7 @@ class AgentDrop |
||
445 | 443 |
@object.short_type |
446 | 444 |
end |
447 | 445 |
|
448 |
- [ |
|
446 |
+ METHODS = [ |
|
449 | 447 |
:name, |
450 | 448 |
:type, |
451 | 449 |
:options, |
@@ -458,7 +456,9 @@ class AgentDrop |
||
458 | 456 |
:disabled, |
459 | 457 |
:keep_events_for, |
460 | 458 |
:propagate_immediately, |
461 |
- ].each { |attr| |
|
459 |
+ ] |
|
460 |
+ |
|
461 |
+ METHODS.each { |attr| |
|
462 | 462 |
define_method(attr) { |
463 | 463 |
@object.__send__(attr) |
464 | 464 |
} unless method_defined?(attr) |
@@ -2,13 +2,11 @@ |
||
2 | 2 |
# in Agents' detail pages. AgentLogs with a `level` of 4 or greater are considered "errors" and automatically update |
3 | 3 |
# Agents' `last_error_log_at` column. These are often used to determine if an Agent is `working?`. |
4 | 4 |
class AgentLog < ActiveRecord::Base |
5 |
- attr_accessible :agent, :inbound_event, :level, :message, :outbound_event |
|
6 |
- |
|
7 | 5 |
belongs_to :agent |
8 |
- belongs_to :inbound_event, :class_name => "Event" |
|
9 |
- belongs_to :outbound_event, :class_name => "Event" |
|
6 |
+ belongs_to :inbound_event, :class_name => "Event", optional: true |
|
7 |
+ belongs_to :outbound_event, :class_name => "Event", optional: true |
|
10 | 8 |
|
11 |
- validates_presence_of :agent, :message |
|
9 |
+ validates_presence_of :message |
|
12 | 10 |
validates_numericality_of :level, :only_integer => true, :greater_than_or_equal_to => 0, :less_than => 5 |
13 | 11 |
|
14 | 12 |
before_validation :scrub_message |
@@ -46,9 +46,14 @@ module Agents |
||
46 | 46 |
"_contents": "tag contents (can be an object for nesting)" |
47 | 47 |
} |
48 | 48 |
|
49 |
- # Ordering events in the output |
|
49 |
+ # Ordering events |
|
50 | 50 |
|
51 |
- #{description_events_order('events in the output')} |
|
51 |
+ #{description_events_order('events')} |
|
52 |
+ |
|
53 |
+ DataOutputAgent will select the last `events_to_show` entries of its received events sorted in the order specified by `events_order`, which is defaulted to the event creation time. |
|
54 |
+ So, if you have multiple source agents that may create many events in a run, you may want to either increase `events_to_show` to have a larger "window", or specify the `events_order` option to an appropriate value (like `date_published`) so events from various sources are properly mixed in the resulted feed. |
|
55 |
+ |
|
56 |
+ There is also an option `events_list_order` that only controls the order of events listed in the final output, without attempting to maintain a total order of received events. It has the same format as `events_order` and is defaulted to `#{Utils.jsonify(DEFAULT_EVENTS_ORDER['events_list_order'])}` so the selected events are listed in reverse order like most popular RSS feeds list their articles. |
|
52 | 57 |
|
53 | 58 |
# Liquid Templating |
54 | 59 |
|
@@ -176,6 +181,60 @@ module Agents |
||
176 | 181 |
interpolated['push_hubs'].presence || [] |
177 | 182 |
end |
178 | 183 |
|
184 |
+ DEFAULT_EVENTS_ORDER = { |
|
185 |
+ 'events_order' => nil, |
|
186 |
+ 'events_list_order' => [["{{_index_}}", "number", true]], |
|
187 |
+ } |
|
188 |
+ |
|
189 |
+ def events_order(key = SortableEvents::EVENTS_ORDER_KEY) |
|
190 |
+ super || DEFAULT_EVENTS_ORDER[key] |
|
191 |
+ end |
|
192 |
+ |
|
193 |
+ def latest_events(reload = false) |
|
194 |
+ received_events = received_events().reorder(id: :asc) |
|
195 |
+ |
|
196 |
+ events = |
|
197 |
+ if (event_ids = memory[:event_ids]) && |
|
198 |
+ memory[:events_order] == events_order && |
|
199 |
+ memory[:events_to_show] >= events_to_show |
|
200 |
+ received_events.where(id: event_ids).to_a |
|
201 |
+ else |
|
202 |
+ memory[:last_event_id] = nil |
|
203 |
+ reload = true |
|
204 |
+ [] |
|
205 |
+ end |
|
206 |
+ |
|
207 |
+ if reload |
|
208 |
+ memory[:events_order] = events_order |
|
209 |
+ memory[:events_to_show] = events_to_show |
|
210 |
+ |
|
211 |
+ new_events = |
|
212 |
+ if last_event_id = memory[:last_event_id] |
|
213 |
+ received_events.where(Event.arel_table[:id].gt(last_event_id)).to_a |
|
214 |
+ else |
|
215 |
+ source_ids.flat_map { |source_id| |
|
216 |
+ # dig twice as many events as the number of |
|
217 |
+ # `events_to_show` |
|
218 |
+ received_events.where(agent_id: source_id). |
|
219 |
+ last(2 * events_to_show) |
|
220 |
+ }.sort_by(&:id) |
|
221 |
+ end |
|
222 |
+ |
|
223 |
+ unless new_events.empty? |
|
224 |
+ memory[:last_event_id] = new_events.last.id |
|
225 |
+ events.concat(new_events) |
|
226 |
+ end |
|
227 |
+ end |
|
228 |
+ |
|
229 |
+ events = sort_events(events).last(events_to_show) |
|
230 |
+ |
|
231 |
+ if reload |
|
232 |
+ memory[:event_ids] = events.map(&:id) |
|
233 |
+ end |
|
234 |
+ |
|
235 |
+ events |
|
236 |
+ end |
|
237 |
+ |
|
179 | 238 |
def receive_web_request(params, method, format) |
180 | 239 |
unless interpolated['secrets'].include?(params['secret']) |
181 | 240 |
if format =~ /json/ |
@@ -185,7 +244,7 @@ module Agents |
||
185 | 244 |
end |
186 | 245 |
end |
187 | 246 |
|
188 |
- source_events = sort_events(received_events.order(id: :desc).limit(events_to_show).to_a) |
|
247 |
+ source_events = sort_events(latest_events(), 'events_list_order') |
|
189 | 248 |
|
190 | 249 |
interpolation_context.stack do |
191 | 250 |
interpolation_context['events'] = source_events |
@@ -252,6 +311,9 @@ module Agents |
||
252 | 311 |
def receive(incoming_events) |
253 | 312 |
url = feed_url(secret: interpolated['secrets'].first, format: :xml) |
254 | 313 |
|
314 |
+ # Reload new events and update cache |
|
315 |
+ latest_events(true) |
|
316 |
+ |
|
255 | 317 |
push_hubs.each do |hub| |
256 | 318 |
push_to_hub(hub, url) |
257 | 319 |
end |
@@ -7,7 +7,9 @@ module Agents |
||
7 | 7 |
cannot_create_events! |
8 | 8 |
|
9 | 9 |
description <<-MD |
10 |
- The Email Digest Agent collects any Events sent to it and sends them all via email when scheduled. |
|
10 |
+ The Email Digest Agent collects any Events sent to it and sends them all via email when scheduled. The number of |
|
11 |
+ used events also relies on the `Keep events` option of the emitting Agent, meaning that if events expire before |
|
12 |
+ this agent is scheduled to run, they will not appear in the email. |
|
11 | 13 |
|
12 | 14 |
By default, the will have a `subject` and an optional `headline` before listing the Events. If the Events' |
13 | 15 |
payloads contain a `message`, that will be highlighted, otherwise everything in |
@@ -37,18 +39,16 @@ module Agents |
||
37 | 39 |
end |
38 | 40 |
|
39 | 41 |
def receive(incoming_events) |
42 |
+ self.memory['events'] ||= [] |
|
40 | 43 |
incoming_events.each do |event| |
41 |
- self.memory['queue'] ||= [] |
|
42 |
- self.memory['queue'] << event.payload |
|
43 |
- self.memory['events'] ||= [] |
|
44 | 44 |
self.memory['events'] << event.id |
45 | 45 |
end |
46 | 46 |
end |
47 | 47 |
|
48 | 48 |
def check |
49 |
- if self.memory['queue'] && self.memory['queue'].length > 0 |
|
50 |
- ids = self.memory['events'].join(",") |
|
51 |
- groups = self.memory['queue'].map { |payload| present(payload) } |
|
49 |
+ if self.memory['events'] && self.memory['events'].length > 0 |
|
50 |
+ payloads = received_events.reorder("events.id ASC").where(id: self.memory['events']).pluck(:payload).to_a |
|
51 |
+ groups = payloads.map { |payload| present(payload) } |
|
52 | 52 |
recipients.each do |recipient| |
53 | 53 |
begin |
54 | 54 |
SystemMailer.send_message( |
@@ -59,13 +59,13 @@ module Agents |
||
59 | 59 |
content_type: interpolated['content_type'], |
60 | 60 |
groups: groups |
61 | 61 |
).deliver_now |
62 |
- log "Sent digest mail to #{recipient} with events [#{ids}]" |
|
62 |
+ |
|
63 |
+ log "Sent digest mail to #{recipient}" |
|
63 | 64 |
rescue => e |
64 |
- error("Error sending digest mail to #{recipient} with events [#{ids}]: #{e.message}") |
|
65 |
+ error("Error sending digest mail to #{recipient}: #{e.message}") |
|
65 | 66 |
raise |
66 | 67 |
end |
67 | 68 |
end |
68 |
- self.memory['queue'] = [] |
|
69 | 69 |
self.memory['events'] = [] |
70 | 70 |
end |
71 | 71 |
end |
@@ -91,7 +91,8 @@ module Agents |
||
91 | 91 |
end |
92 | 92 |
|
93 | 93 |
def receive(incoming_events) |
94 |
- incoming_events.each do |event| |
|
94 |
+ require 'google_calendar' |
|
95 |
+ incoming_events.each do |event| |
|
95 | 96 |
calendar = GoogleCalendar.new(interpolate_options(options, event), Rails.logger) |
96 | 97 |
|
97 | 98 |
calendar_event = JSON.parse(calendar.publish_as(interpolated(event)['calendar_id'], event.payload["message"]).response.body) |
@@ -1,3 +1,5 @@ |
||
1 |
+require 'time_tracker' |
|
2 |
+ |
|
1 | 3 |
module Agents |
2 | 4 |
|
3 | 5 |
class HttpStatusAgent < Agent |
@@ -1,6 +1,9 @@ |
||
1 | 1 |
module Agents |
2 | 2 |
class PostAgent < Agent |
3 | 3 |
include WebRequestConcern |
4 |
+ include FileHandling |
|
5 |
+ |
|
6 |
+ consumes_file_pointer! |
|
4 | 7 |
|
5 | 8 |
MIME_RE = /\A\w+\/.+\z/ |
6 | 9 |
|
@@ -8,38 +11,44 @@ module Agents |
||
8 | 11 |
no_bulk_receive! |
9 | 12 |
default_schedule "never" |
10 | 13 |
|
11 |
- description <<-MD |
|
12 |
- A Post Agent receives events from other agents (or runs periodically), merges those events with the [Liquid-interpolated](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) contents of `payload`, and sends the results as POST (or GET) requests to a specified url. To skip merging in the incoming event, but still send the interpolated payload, set `no_merge` to `true`. |
|
14 |
+ description do |
|
15 |
+ <<-MD |
|
16 |
+ A Post Agent receives events from other agents (or runs periodically), merges those events with the [Liquid-interpolated](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) contents of `payload`, and sends the results as POST (or GET) requests to a specified url. To skip merging in the incoming event, but still send the interpolated payload, set `no_merge` to `true`. |
|
13 | 17 |
|
14 |
- The `post_url` field must specify where you would like to send requests. Please include the URI scheme (`http` or `https`). |
|
18 |
+ The `post_url` field must specify where you would like to send requests. Please include the URI scheme (`http` or `https`). |
|
15 | 19 |
|
16 |
- The `method` used can be any of `get`, `post`, `put`, `patch`, and `delete`. |
|
20 |
+ The `method` used can be any of `get`, `post`, `put`, `patch`, and `delete`. |
|
17 | 21 |
|
18 |
- By default, non-GETs will be sent with form encoding (`application/x-www-form-urlencoded`). |
|
22 |
+ By default, non-GETs will be sent with form encoding (`application/x-www-form-urlencoded`). |
|
19 | 23 |
|
20 |
- Change `content_type` to `json` to send JSON instead. |
|
24 |
+ Change `content_type` to `json` to send JSON instead. |
|
21 | 25 |
|
22 |
- Change `content_type` to `xml` to send XML, where the name of the root element may be specified using `xml_root`, defaulting to `post`. |
|
26 |
+ Change `content_type` to `xml` to send XML, where the name of the root element may be specified using `xml_root`, defaulting to `post`. |
|
23 | 27 |
|
24 |
- When `content_type` contains a [MIME](https://en.wikipedia.org/wiki/Media_type) type, and `payload` is a string, its interpolated value will be sent as a string in the HTTP request's body and the request's `Content-Type` HTTP header will be set to `content_type`. When `payload` is a string `no_merge` has to be set to `true`. |
|
28 |
+ When `content_type` contains a [MIME](https://en.wikipedia.org/wiki/Media_type) type, and `payload` is a string, its interpolated value will be sent as a string in the HTTP request's body and the request's `Content-Type` HTTP header will be set to `content_type`. When `payload` is a string `no_merge` has to be set to `true`. |
|
25 | 29 |
|
26 |
- If `emit_events` is set to `true`, the server response will be emitted as an Event and can be fed to a WebsiteAgent for parsing (using its `data_from_event` and `type` options). No data processing |
|
27 |
- will be attempted by this Agent, so the Event's "body" value will always be raw text. |
|
28 |
- The Event will also have a "headers" hash and a "status" integer value. |
|
29 |
- Set `event_headers_style` to one of the following values to normalize the keys of "headers" for downstream agents' convenience: |
|
30 |
+ If `emit_events` is set to `true`, the server response will be emitted as an Event and can be fed to a WebsiteAgent for parsing (using its `data_from_event` and `type` options). No data processing |
|
31 |
+ will be attempted by this Agent, so the Event's "body" value will always be raw text. |
|
32 |
+ The Event will also have a "headers" hash and a "status" integer value. |
|
33 |
+ Set `event_headers_style` to one of the following values to normalize the keys of "headers" for downstream agents' convenience: |
|
30 | 34 |
|
31 |
- * `capitalized` (default) - Header names are capitalized; e.g. "Content-Type" |
|
32 |
- * `downcased` - Header names are downcased; e.g. "content-type" |
|
33 |
- * `snakecased` - Header names are snakecased; e.g. "content_type" |
|
34 |
- * `raw` - Backward compatibility option to leave them unmodified from what the underlying HTTP library returns. |
|
35 |
+ * `capitalized` (default) - Header names are capitalized; e.g. "Content-Type" |
|
36 |
+ * `downcased` - Header names are downcased; e.g. "content-type" |
|
37 |
+ * `snakecased` - Header names are snakecased; e.g. "content_type" |
|
38 |
+ * `raw` - Backward compatibility option to leave them unmodified from what the underlying HTTP library returns. |
|
35 | 39 |
|
36 |
- Other Options: |
|
40 |
+ Other Options: |
|
37 | 41 |
|
38 |
- * `headers` - When present, it should be a hash of headers to send with the request. |
|
39 |
- * `basic_auth` - Specify HTTP basic auth parameters: `"username:password"`, or `["username", "password"]`. |
|
40 |
- * `disable_ssl_verification` - Set to `true` to disable ssl verification. |
|
41 |
- * `user_agent` - A custom User-Agent name (default: "Faraday v#{Faraday::VERSION}"). |
|
42 |
- MD |
|
42 |
+ * `headers` - When present, it should be a hash of headers to send with the request. |
|
43 |
+ * `basic_auth` - Specify HTTP basic auth parameters: `"username:password"`, or `["username", "password"]`. |
|
44 |
+ * `disable_ssl_verification` - Set to `true` to disable ssl verification. |
|
45 |
+ * `user_agent` - A custom User-Agent name (default: "Faraday v#{Faraday::VERSION}"). |
|
46 |
+ |
|
47 |
+ #{receiving_file_handling_agent_description} |
|
48 |
+ |
|
49 |
+ When receiving a `file_pointer` the request will be sent with multipart encoding (`multipart/form-data`) and `content_type` is ignored. `upload_key` can be used to specify the parameter in which the file will be sent, it defaults to `file`. |
|
50 |
+ MD |
|
51 |
+ end |
|
43 | 52 |
|
44 | 53 |
event_description <<-MD |
45 | 54 |
Events look like this: |
@@ -125,9 +134,9 @@ module Agents |
||
125 | 134 |
interpolate_with(event) do |
126 | 135 |
outgoing = interpolated['payload'].presence || {} |
127 | 136 |
if boolify(interpolated['no_merge']) |
128 |
- handle outgoing, event.payload, headers(interpolated[:headers]) |
|
137 |
+ handle outgoing, event, headers(interpolated[:headers]) |
|
129 | 138 |
else |
130 |
- handle outgoing.merge(event.payload), event.payload, headers(interpolated[:headers]) |
|
139 |
+ handle outgoing.merge(event.payload), event, headers(interpolated[:headers]) |
|
131 | 140 |
end |
132 | 141 |
end |
133 | 142 |
end |
@@ -162,8 +171,8 @@ module Agents |
||
162 | 171 |
} |
163 | 172 |
end |
164 | 173 |
|
165 |
- def handle(data, payload = {}, headers) |
|
166 |
- url = interpolated(payload)[:post_url] |
|
174 |
+ def handle(data, event = Event.new, headers) |
|
175 |
+ url = interpolated(event.payload)[:post_url] |
|
167 | 176 |
|
168 | 177 |
case method |
169 | 178 |
when 'get', 'delete' |
@@ -171,13 +180,21 @@ module Agents |
||
171 | 180 |
when 'post', 'put', 'patch' |
172 | 181 |
params = nil |
173 | 182 |
|
174 |
- case (content_type = interpolated(payload)['content_type']) |
|
183 |
+ content_type = |
|
184 |
+ if has_file_pointer?(event) |
|
185 |
+ data[interpolated(event.payload)['upload_key'].presence || 'file'] = get_upload_io(event) |
|
186 |
+ nil |
|
187 |
+ else |
|
188 |
+ interpolated(event.payload)['content_type'] |
|
189 |
+ end |
|
190 |
+ |
|
191 |
+ case content_type |
|
175 | 192 |
when 'json' |
176 | 193 |
headers['Content-Type'] = 'application/json; charset=utf-8' |
177 | 194 |
body = data.to_json |
178 | 195 |
when 'xml' |
179 | 196 |
headers['Content-Type'] = 'text/xml; charset=utf-8' |
180 |
- body = data.to_xml(root: (interpolated(payload)[:xml_root] || 'post')) |
|
197 |
+ body = data.to_xml(root: (interpolated(event.payload)[:xml_root] || 'post')) |
|
181 | 198 |
when MIME_RE |
182 | 199 |
headers['Content-Type'] = content_type |
183 | 200 |
body = data.to_s |
@@ -12,42 +12,41 @@ module Agents |
||
12 | 12 |
|
13 | 13 |
**You need a Pushover API Token:** [https://pushover.net/apps/build](https://pushover.net/apps/build) |
14 | 14 |
|
15 |
- **You must provide** a `message` or `text` key that will contain the body of the notification. This can come from an event or be set as a default. Pushover API has a `512` Character Limit including `title`. `message` will be truncated. |
|
16 |
- |
|
17 | 15 |
* `token`: your application's API token |
18 | 16 |
* `user`: the user or group key (not e-mail address). |
19 | 17 |
* `expected_receive_period_in_days`: is maximum number of days that you would expect to pass between events being received by this agent. |
20 | 18 |
|
21 |
- Your event can provide any of the following optional parameters or you can provide defaults: |
|
19 |
+ The following options are all Liquid templates whose evaluated values will be posted to the Pushover API. Only the `message` parameter is required, and if it is blank API call is omitted. |
|
20 |
+ |
|
21 |
+ Pushover API has a `512` Character Limit including `title`. `message` will be truncated. |
|
22 | 22 |
|
23 |
+ * `message` - your message (required) |
|
23 | 24 |
* `device` - your user's device name to send the message directly to that device, rather than all of the user's devices |
24 | 25 |
* `title` or `subject` - your notification's title |
25 | 26 |
* `url` - a supplementary URL to show with your message - `512` Character Limit |
26 | 27 |
* `url_title` - a title for your supplementary URL, otherwise just the URL is shown - `100` Character Limit |
28 |
+ * `timestamp` - a [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time) of your message's date and time to display to the user, rather than the time your message is received by the Pushover API. |
|
27 | 29 |
* `priority` - send as `-1` to always send as a quiet notification, `0` is default, `1` to display as high-priority and bypass the user's quiet hours, or `2` for emergency priority: [Please read Pushover Docs on Emergency Priority](https://pushover.net/api#priority) |
28 | 30 |
* `sound` - the name of one of the sounds supported by device clients to override the user's default sound choice. [See PushOver docs for sound options.](https://pushover.net/api#sounds) |
29 | 31 |
* `retry` - Required for emergency priority - Specifies how often (in seconds) the Pushover servers will send the same notification to the user. Minimum value: `30` |
30 | 32 |
* `expire` - Required for emergency priority - Specifies how many seconds your notification will continue to be retried for (every retry seconds). Maximum value: `86400` |
31 | 33 |
|
32 |
- Your event can also pass along a timestamp parameter: |
|
33 |
- |
|
34 |
- * `timestamp` - a [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time) of your message's date and time to display to the user, rather than the time your message is received by the Pushover API. |
|
35 |
- |
|
36 | 34 |
MD |
37 | 35 |
|
38 | 36 |
def default_options |
39 | 37 |
{ |
40 | 38 |
'token' => '', |
41 | 39 |
'user' => '', |
42 |
- 'message' => 'a default message', |
|
43 |
- 'device' => '', |
|
44 |
- 'title' => '', |
|
45 |
- 'url' => '', |
|
46 |
- 'url_title' => '', |
|
47 |
- 'priority' => '0', |
|
48 |
- 'sound' => 'pushover', |
|
49 |
- 'retry' => '0', |
|
50 |
- 'expire' => '0', |
|
40 |
+ 'message' => '{{ message }}', |
|
41 |
+ 'device' => '{{ device }}', |
|
42 |
+ 'title' => '{{ title }}', |
|
43 |
+ 'url' => '{{ url }}', |
|
44 |
+ 'url_title' => '{{ url_title }}', |
|
45 |
+ 'priority' => '{{ priority }}', |
|
46 |
+ 'timestamp' => '{{ timestamp }}', |
|
47 |
+ 'sound' => '{{ sound }}', |
|
48 |
+ 'retry' => '{{ retry }}', |
|
49 |
+ 'expire' => '{{ expire }}', |
|
51 | 50 |
'expected_receive_period_in_days' => '1' |
52 | 51 |
} |
53 | 52 |
end |
@@ -60,38 +59,43 @@ module Agents |
||
60 | 59 |
|
61 | 60 |
def receive(incoming_events) |
62 | 61 |
incoming_events.each do |event| |
63 |
- payload_interpolated = interpolated(event) |
|
64 |
- message = (event.payload['message'].presence || event.payload['text'].presence || payload_interpolated['message']).to_s |
|
65 |
- if message.present? |
|
66 |
- post_params = { |
|
67 |
- 'token' => payload_interpolated['token'], |
|
68 |
- 'user' => payload_interpolated['user'], |
|
69 |
- 'message' => message |
|
70 |
- } |
|
71 |
- |
|
72 |
- post_params['device'] = event.payload['device'].presence || payload_interpolated['device'] |
|
73 |
- post_params['title'] = event.payload['title'].presence || event.payload['subject'].presence || payload_interpolated['title'] |
|
74 |
- |
|
75 |
- url = (event.payload['url'].presence || payload_interpolated['url'] || '').to_s |
|
76 |
- url = url.slice 0..512 |
|
77 |
- post_params['url'] = url |
|
78 |
- |
|
79 |
- url_title = (event.payload['url_title'].presence || payload_interpolated['url_title']).to_s |
|
80 |
- url_title = url_title.slice 0..100 |
|
81 |
- post_params['url_title'] = url_title |
|
82 |
- |
|
83 |
- post_params['priority'] = (event.payload['priority'].presence || payload_interpolated['priority']).to_i |
|
84 |
- |
|
85 |
- if event.payload.has_key? 'timestamp' |
|
86 |
- post_params['timestamp'] = (event.payload['timestamp']).to_s |
|
62 |
+ interpolate_with(event) do |
|
63 |
+ post_params = {} |
|
64 |
+ |
|
65 |
+ # required parameters |
|
66 |
+ %w[ |
|
67 |
+ token |
|
68 |
+ user |
|
69 |
+ message |
|
70 |
+ ].all? { |key| |
|
71 |
+ if value = String.try_convert(interpolated[key].presence) |
|
72 |
+ post_params[key] = value |
|
73 |
+ end |
|
74 |
+ } or next |
|
75 |
+ |
|
76 |
+ # optional parameters |
|
77 |
+ %w[ |
|
78 |
+ device |
|
79 |
+ title |
|
80 |
+ url |
|
81 |
+ url_title |
|
82 |
+ priority |
|
83 |
+ timestamp |
|
84 |
+ sound |
|
85 |
+ retry |
|
86 |
+ expire |
|
87 |
+ ].each do |key| |
|
88 |
+ if value = String.try_convert(interpolated[key].presence) |
|
89 |
+ case key |
|
90 |
+ when 'url' |
|
91 |
+ value.slice!(512..-1) |
|
92 |
+ when 'url_title' |
|
93 |
+ value.slice!(100..-1) |
|
94 |
+ end |
|
95 |
+ post_params[key] = value |
|
96 |
+ end |
|
87 | 97 |
end |
88 | 98 |
|
89 |
- post_params['sound'] = (event.payload['sound'].presence || payload_interpolated['sound']).to_s |
|
90 |
- |
|
91 |
- post_params['retry'] = (event.payload['retry'].presence || payload_interpolated['retry']).to_i |
|
92 |
- |
|
93 |
- post_params['expire'] = (event.payload['expire'].presence || payload_interpolated['expire']).to_i |
|
94 |
- |
|
95 | 99 |
send_notification(post_params) |
96 | 100 |
end |
97 | 101 |
end |
@@ -102,7 +106,7 @@ module Agents |
||
102 | 106 |
end |
103 | 107 |
|
104 | 108 |
def send_notification(post_params) |
105 |
- response = HTTParty.post(API_URL, :query => post_params) |
|
109 |
+ response = HTTParty.post(API_URL, query: post_params) |
|
106 | 110 |
puts response |
107 | 111 |
end |
108 | 112 |
end |
@@ -1,6 +1,3 @@ |
||
1 |
-require 'rss' |
|
2 |
-require 'feed-normalizer' |
|
3 |
- |
|
4 | 1 |
module Agents |
5 | 2 |
class RssAgent < Agent |
6 | 3 |
include WebRequestConcern |
@@ -9,21 +6,23 @@ module Agents |
||
9 | 6 |
can_dry_run! |
10 | 7 |
default_schedule "every_1d" |
11 | 8 |
|
9 |
+ gem_dependency_check { defined?(Feedjira::Feed) } |
|
10 |
+ |
|
12 | 11 |
DEFAULT_EVENTS_ORDER = [['{{date_published}}', 'time'], ['{{last_updated}}', 'time']] |
13 | 12 |
|
14 | 13 |
description do |
15 | 14 |
<<-MD |
16 | 15 |
The RSS Agent consumes RSS feeds and emits events when they change. |
17 | 16 |
|
18 |
- This Agent is fairly simple, using [feed-normalizer](https://github.com/aasmith/feed-normalizer) as a base. For complex feeds |
|
19 |
- with additional field types, we recommend using a WebsiteAgent. See [this example](https://github.com/cantino/huginn/wiki/Agent-configuration-examples#itunes-trailers). |
|
17 |
+ This agent, using [Feedjira](https://github.com/feedjira/feedjira) as a base, can parse various types of RSS and Atom feeds and has some special handlers for FeedBurner, iTunes RSS, and so on. However, supported fields are limited by its general and abstract nature. For complex feeds with additional field types, we recommend using a WebsiteAgent. See [this example](https://github.com/cantino/huginn/wiki/Agent-configuration-examples#itunes-trailers). |
|
20 | 18 |
|
21 | 19 |
If you want to *output* an RSS feed, use the DataOutputAgent. |
22 | 20 |
|
23 | 21 |
Options: |
24 | 22 |
|
25 | 23 |
* `url` - The URL of the RSS feed (an array of URLs can also be used; items with identical guids across feeds will be considered duplicates). |
26 |
- * `clean` - Attempt to use [feed-normalizer](https://github.com/aasmith/feed-normalizer)'s' `clean!` method to cleanup HTML in the feed. Set to `true` to use. |
|
24 |
+ * `include_feed_info` - Set to `true` to include feed information in each event. |
|
25 |
+ * `clean` - Set to `true` to sanitize `description` and `content` as HTML fragments, removing unknown/unsafe elements and attributes. |
|
27 | 26 |
* `expected_update_period_in_days` - How often you expect this RSS feed to change. If more than this amount of time passes without an update, the Agent will mark itself as not working. |
28 | 27 |
* `headers` - When present, it should be a hash of headers to send with the request. |
29 | 28 |
* `basic_auth` - Specify HTTP basic auth parameters: `"username:password"`, or `["username", "password"]`. |
@@ -53,18 +52,46 @@ module Agents |
||
53 | 52 |
Events look like: |
54 | 53 |
|
55 | 54 |
{ |
55 |
+ "feed": { |
|
56 |
+ "id": "...", |
|
57 |
+ "type": "atom", |
|
58 |
+ "generator": "...", |
|
59 |
+ "url": "http://example.com/", |
|
60 |
+ "links": [ |
|
61 |
+ { "href": "http://example.com/", "rel": "alternate", "type": "text/html" }, |
|
62 |
+ { "href": "http://example.com/index.atom", "rel": "self", "type": "application/atom+xml" } |
|
63 |
+ ], |
|
64 |
+ "title": "Some site title", |
|
65 |
+ "description": "Some site description", |
|
66 |
+ "copyright": "...", |
|
67 |
+ "icon": "http://example.com/icon.png", |
|
68 |
+ "authors": [ "..." ], |
|
69 |
+ "date_published": "2014-09-11T01:30:00-07:00", |
|
70 |
+ "last_updated": "2014-09-11T01:30:00-07:00" |
|
71 |
+ }, |
|
56 | 72 |
"id": "829f845279611d7925146725317b868d", |
57 |
- "date_published": "2014-09-11 01:30:00 -0700", |
|
58 |
- "last_updated": "Thu, 11 Sep 2014 01:30:00 -0700", |
|
59 | 73 |
"url": "http://example.com/...", |
60 | 74 |
"urls": [ "http://example.com/..." ], |
75 |
+ "links": [ |
|
76 |
+ { "href": "http://example.com/...", "rel": "alternate" }, |
|
77 |
+ ], |
|
78 |
+ "title": "Some title", |
|
61 | 79 |
"description": "Some description", |
62 | 80 |
"content": "Some content", |
63 |
- "title": "Some title", |
|
64 |
- "authors": [ ... ], |
|
65 |
- "categories": [ ... ] |
|
81 |
+ "authors": [ "Some Author <email@address>" ], |
|
82 |
+ "categories": [ "..." ], |
|
83 |
+ "enclosure": { |
|
84 |
+ "url" => "http://example.com/file.mp3", "type" => "audio/mpeg", "length" => "123456789" |
|
85 |
+ }, |
|
86 |
+ "date_published": "2014-09-11T01:30:00-0700", |
|
87 |
+ "last_updated": "2014-09-11T01:30:00-0700" |
|
66 | 88 |
} |
67 | 89 |
|
90 |
+ Some notes: |
|
91 |
+ |
|
92 |
+ - The `feed` key is present only if `include_feed_info` is set to true. |
|
93 |
+ - Each element in `authors` is a string normalized in the format "*name* <*email*> (*url*)", where each space-separated part is optional. |
|
94 |
+ - Timestamps are converted to the ISO 8601 format. |
|
68 | 95 |
MD |
69 | 96 |
|
70 | 97 |
def working? |
@@ -82,8 +109,12 @@ module Agents |
||
82 | 109 |
validate_events_order |
83 | 110 |
end |
84 | 111 |
|
85 |
- def events_order |
|
86 |
- super.presence || DEFAULT_EVENTS_ORDER |
|
112 |
+ def events_order(key = SortableEvents::EVENTS_ORDER_KEY) |
|
113 |
+ if key == SortableEvents::EVENTS_ORDER_KEY |
|
114 |
+ super.presence || DEFAULT_EVENTS_ORDER |
|
115 |
+ else |
|
116 |
+ raise ArgumentError, "unsupported key: #{key}" |
|
117 |
+ end |
|
87 | 118 |
end |
88 | 119 |
|
89 | 120 |
def check |
@@ -100,8 +131,7 @@ module Agents |
||
100 | 131 |
begin |
101 | 132 |
response = faraday.get(url) |
102 | 133 |
if response.success? |
103 |
- feed = FeedNormalizer::FeedNormalizer.parse(response.body, loose: true) |
|
104 |
- feed.clean! if boolify(interpolated['clean']) |
|
134 |
+ feed = Feedjira::Feed.parse(response.body) |
|
105 | 135 |
new_events.concat feed_to_events(feed) |
106 | 136 |
else |
107 | 137 |
error "Failed to fetch #{url}: #{response.inspect}" |
@@ -124,10 +154,6 @@ module Agents |
||
124 | 154 |
log "Fetched #{urls.to_sentence} and created #{created_event_count} event(s)." |
125 | 155 |
end |
126 | 156 |
|
127 |
- def get_entry_id(entry) |
|
128 |
- entry.id.presence || Digest::MD5.hexdigest(entry.content) |
|
129 |
- end |
|
130 |
- |
|
131 | 157 |
def check_and_track(entry_id) |
132 | 158 |
memory['seen_ids'] ||= [] |
133 | 159 |
if memory['seen_ids'].include?(entry_id) |
@@ -139,21 +165,71 @@ module Agents |
||
139 | 165 |
end |
140 | 166 |
end |
141 | 167 |
|
168 |
+ unless dependencies_missing? |
|
169 |
+ require 'feedjira_extension' |
|
170 |
+ end |
|
171 |
+ |
|
172 |
+ def feed_data(feed) |
|
173 |
+ type = |
|
174 |
+ case feed.class.name |
|
175 |
+ when /Atom/ |
|
176 |
+ 'atom' |
|
177 |
+ else |
|
178 |
+ 'rss' |
|
179 |
+ end |
|
180 |
+ |
|
181 |
+ { |
|
182 |
+ id: feed.feed_id, |
|
183 |
+ type: type, |
|
184 |
+ url: feed.url, |
|
185 |
+ links: feed.links, |
|
186 |
+ title: feed.title, |
|
187 |
+ description: feed.description, |
|
188 |
+ copyright: feed.copyright, |
|
189 |
+ generator: feed.generator, |
|
190 |
+ icon: feed.icon, |
|
191 |
+ authors: feed.authors, |
|
192 |
+ date_published: feed.date_published, |
|
193 |
+ last_updated: feed.last_updated, |
|
194 |
+ } |
|
195 |
+ end |
|
196 |
+ |
|
197 |
+ def entry_data(entry) |
|
198 |
+ { |
|
199 |
+ id: entry.id, |
|
200 |
+ url: entry.url, |
|
201 |
+ urls: entry.links.map(&:href), |
|
202 |
+ links: entry.links, |
|
203 |
+ title: entry.title, |
|
204 |
+ description: clean_fragment(entry.summary), |
|
205 |
+ content: clean_fragment(entry.content || entry.summary), |
|
206 |
+ image: entry.try(:image), |
|
207 |
+ enclosure: entry.enclosure, |
|
208 |
+ authors: entry.authors, |
|
209 |
+ categories: Array(entry.try(:categories)), |
|
210 |
+ date_published: entry.date_published, |
|
211 |
+ last_updated: entry.last_updated, |
|
212 |
+ } |
|
213 |
+ end |
|
214 |
+ |
|
142 | 215 |
def feed_to_events(feed) |
216 |
+ payload_base = {} |
|
217 |
+ |
|
218 |
+ if boolify(interpolated['include_feed_info']) |
|
219 |
+ payload_base[:feed] = feed_data(feed) |
|
220 |
+ end |
|
221 |
+ |
|
143 | 222 |
feed.entries.map { |entry| |
144 |
- Event.new(payload: { |
|
145 |
- id: get_entry_id(entry), |
|
146 |
- date_published: entry.date_published, |
|
147 |
- last_updated: entry.last_updated, |
|
148 |
- url: entry.url, |
|
149 |
- urls: entry.urls, |
|
150 |
- description: entry.description, |
|
151 |
- content: entry.content, |
|
152 |
- title: entry.title, |
|
153 |
- authors: entry.authors, |
|
154 |
- categories: entry.categories |
|
155 |
- }) |
|
223 |
+ Event.new(payload: payload_base.merge(entry_data(entry))) |
|
156 | 224 |
} |
157 | 225 |
end |
226 |
+ |
|
227 |
+ def clean_fragment(fragment) |
|
228 |
+ if boolify(interpolated['clean']) && fragment.present? |
|
229 |
+ Loofah.scrub_fragment(fragment, :prune).to_s |
|
230 |
+ else |
|
231 |
+ fragment |
|
232 |
+ end |
|
233 |
+ end |
|
158 | 234 |
end |
159 | 235 |
end |
@@ -8,7 +8,7 @@ module Agents |
||
8 | 8 |
description <<-MD |
9 | 9 |
The Trigger Agent will watch for a specific value in an Event payload. |
10 | 10 |
|
11 |
- The `rules` array contains hashes of `path`, `value`, and `type`. The `path` value is a dotted path through a hash in [JSONPaths](http://goessner.net/articles/JsonPath/) syntax. |
|
11 |
+ The `rules` array contains hashes of `path`, `value`, and `type`. The `path` value is a dotted path through a hash in [JSONPaths](http://goessner.net/articles/JsonPath/) syntax. For simple events, this is usually just the name of the field you want, like 'text' for the text key of the event. |
|
12 | 12 |
|
13 | 13 |
The `type` can be one of #{VALID_COMPARISON_TYPES.map { |t| "`#{t}`" }.to_sentence} and compares with the `value`. Note that regex patterns are matched case insensitively. If you want case sensitive matching, prefix your pattern with `(?-i)`. |
14 | 14 |
|
@@ -16,6 +16,7 @@ module Agents |
||
16 | 16 |
Set `expected_receive_period_in_days` to the maximum amount of time that you'd expect to pass between Events being received by this Agent. |
17 | 17 |
Set `retweet` to either true or false. |
18 | 18 |
Set `favorite` to either true or false. |
19 |
+ Set `emit_error_events` to true to emit an Event when the action failed, otherwise the action will be retried. |
|
19 | 20 |
MD |
20 | 21 |
|
21 | 22 |
def validate_options |
@@ -25,6 +26,9 @@ module Agents |
||
25 | 26 |
unless retweet? || favorite? |
26 | 27 |
errors.add(:base, "at least one action must be true") |
27 | 28 |
end |
29 |
+ if emit_error_events?.nil? |
|
30 |
+ errors.add(:base, "emit_error_events must be set to 'true' or 'false'") |
|
31 |
+ end |
|
28 | 32 |
end |
29 | 33 |
|
30 | 34 |
def working? |
@@ -36,6 +40,7 @@ module Agents |
||
36 | 40 |
'expected_receive_period_in_days' => '2', |
37 | 41 |
'favorite' => 'false', |
38 | 42 |
'retweet' => 'true', |
43 |
+ 'emit_error_events' => 'false' |
|
39 | 44 |
} |
40 | 45 |
end |
41 | 46 |
|
@@ -47,6 +52,10 @@ module Agents |
||
47 | 52 |
boolify(options['favorite']) |
48 | 53 |
end |
49 | 54 |
|
55 |
+ def emit_error_events? |
|
56 |
+ boolify(options['emit_error_events']) |
|
57 |
+ end |
|
58 |
+ |
|
50 | 59 |
def receive(incoming_events) |
51 | 60 |
tweets = tweets_from_events(incoming_events) |
52 | 61 |
|
@@ -54,6 +63,7 @@ module Agents |
||
54 | 63 |
twitter.favorite(tweets) if favorite? |
55 | 64 |
twitter.retweet(tweets) if retweet? |
56 | 65 |
rescue Twitter::Error => e |
66 |
+ raise e unless emit_error_events? |
|
57 | 67 |
create_event :payload => { |
58 | 68 |
'success' => false, |
59 | 69 |
'error' => e.message, |
@@ -71,4 +81,3 @@ module Agents |
||
71 | 81 |
end |
72 | 82 |
end |
73 | 83 |
end |
74 |
- |
@@ -14,17 +14,20 @@ module Agents |
||
14 | 14 |
|
15 | 15 |
You also must select `which_day` you would like to get the weather for where the number 0 is for today and 1 is for tomorrow and so on. Weather is only returned for 1 week at a time. |
16 | 16 |
|
17 |
- The weather can be provided by either Wunderground or ForecastIO. To choose which `service` to use, enter either `forecastio` or `wunderground`. |
|
17 |
+ The weather can be provided by either Wunderground or Dark Sky. To choose which `service` to use, enter either `darksky` or `wunderground`. |
|
18 | 18 |
|
19 |
- The `location` can be a US zipcode, or any location that Wunderground supports. To find one, search [wunderground.com](http://wunderground.com) and copy the location part of the URL. For example, a result for San Francisco gives `http://www.wunderground.com/US/CA/San_Francisco.html` and London, England gives `http://www.wunderground.com/q/zmw:00000.1.03772`. The locations in each are `US/CA/San_Francisco` and `zmw:00000.1.03772`, respectively. |
|
19 |
+ The `location` should be: |
|
20 | 20 |
|
21 |
- If you plan on using ForecastIO, the `location` must be a comma-separated string of co-ordinates (longitude, latitude). For example, San Francisco would be `37.7771,-122.4196`. |
|
21 |
+ * For Wunderground: A US zipcode, or any location that Wunderground supports. To find one, search [wunderground.com](http://wunderground.com) and copy the location part of the URL. For example, a result for San Francisco gives `http://www.wunderground.com/US/CA/San_Francisco.html` and London, England gives `http://www.wunderground.com/q/zmw:00000.1.03772`. The locations in each are `US/CA/San_Francisco` and `zmw:00000.1.03772`, respectively. |
|
22 |
+ * For Dark Sky: `location` must be a comma-separated string of co-ordinates (longitude, latitude). For example, San Francisco would be `37.7771,-122.4196`. |
|
22 | 23 |
|
23 | 24 |
You must setup an [API key for Wunderground](http://www.wunderground.com/weather/api/) in order to use this Agent with Wunderground. |
24 | 25 |
|
25 |
- You must setup an [API key for Forecast](https://developer.forecast.io/) in order to use this Agent with ForecastIO. |
|
26 |
+ You must setup an [API key for Dark Sky](https://darksky.net/dev/) in order to use this Agent with Dark Sky. |
|
26 | 27 |
|
27 | 28 |
Set `expected_update_period_in_days` to the maximum amount of time that you'd expect to pass between Events being created by this Agent. |
29 |
+ |
|
30 |
+ If you want to see the returned texts in your language, then set the `language` parameter in ISO 639-1 format. |
|
28 | 31 |
MD |
29 | 32 |
|
30 | 33 |
event_description <<-MD |
@@ -55,11 +58,11 @@ module Agents |
||
55 | 58 |
default_schedule "8pm" |
56 | 59 |
|
57 | 60 |
def working? |
58 |
- event_created_within?((interpolated['expected_update_period_in_days'].presence || 2).to_i) && !recent_error_logs? |
|
61 |
+ event_created_within?((interpolated['expected_update_period_in_days'].presence || 2).to_i) && !recent_error_logs? && key_setup? |
|
59 | 62 |
end |
60 | 63 |
|
61 | 64 |
def key_setup? |
62 |
- interpolated['api_key'].present? && interpolated['api_key'] != "your-key" |
|
65 |
+ interpolated['api_key'].present? && interpolated['api_key'] != "your-key" && interpolated['api_key'] != "put-your-key-here" |
|
63 | 66 |
end |
64 | 67 |
|
65 | 68 |
def default_options |
@@ -68,10 +71,11 @@ module Agents |
||
68 | 71 |
'api_key' => 'your-key', |
69 | 72 |
'location' => '94103', |
70 | 73 |
'which_day' => '1', |
74 |
+ 'language' => 'EN', |
|
71 | 75 |
'expected_update_period_in_days' => '2' |
72 | 76 |
} |
73 | 77 |
end |
74 |
- |
|
78 |
+ |
|
75 | 79 |
def check |
76 | 80 |
if key_setup? |
77 | 81 |
create_event :payload => model(weather_provider, which_day).merge('location' => location) |
@@ -79,7 +83,7 @@ module Agents |
||
79 | 83 |
end |
80 | 84 |
|
81 | 85 |
private |
82 |
- |
|
86 |
+ |
|
83 | 87 |
def weather_provider |
84 | 88 |
interpolated["service"].presence || "wunderground" |
85 | 89 |
end |
@@ -92,30 +96,40 @@ module Agents |
||
92 | 96 |
interpolated["location"].presence || interpolated["zipcode"] |
93 | 97 |
end |
94 | 98 |
|
99 |
+ def language |
|
100 |
+ interpolated['language'].presence || 'EN' |
|
101 |
+ end |
|
102 |
+ |
|
95 | 103 |
def validate_options |
96 |
- errors.add(:base, "service must be set to 'forecastio' or 'wunderground'") unless ["forecastio", "wunderground"].include?(weather_provider) |
|
104 |
+ errors.add(:base, "service must be set to 'darksky' or 'wunderground'") unless %w[darksky forecastio wunderground].include?(weather_provider) |
|
97 | 105 |
errors.add(:base, "location is required") unless location.present? |
98 |
- errors.add(:base, "api_key is required") unless key_setup? |
|
106 |
+ errors.add(:base, "api_key is required") unless interpolated['api_key'].present? |
|
99 | 107 |
errors.add(:base, "which_day selection is required") unless which_day.present? |
100 | 108 |
end |
101 | 109 |
|
102 | 110 |
def wunderground |
103 |
- Wunderground.new(interpolated['api_key']).forecast_for(location)['forecast']['simpleforecast']['forecastday'] if key_setup? |
|
111 |
+ if key_setup? |
|
112 |
+ forecast = Wunderground.new(interpolated['api_key'], language: language.upcase).forecast_for(location) |
|
113 |
+ merged = {} |
|
114 |
+ forecast['forecast']['simpleforecast']['forecastday'].each { |daily| merged[daily['period']] = daily } |
|
115 |
+ forecast['forecast']['txt_forecast']['forecastday'].each { |daily| (merged[daily['period']] || {}).merge!(daily) } |
|
116 |
+ merged |
|
117 |
+ end |
|
104 | 118 |
end |
105 | 119 |
|
106 |
- def forecastio |
|
120 |
+ def dark_sky |
|
107 | 121 |
if key_setup? |
108 | 122 |
ForecastIO.api_key = interpolated['api_key'] |
109 | 123 |
lat, lng = location.split(',') |
110 |
- ForecastIO.forecast(lat,lng)['daily']['data'] |
|
124 |
+ ForecastIO.forecast(lat, lng, params: {lang: language.downcase})['daily']['data'] |
|
111 | 125 |
end |
112 | 126 |
end |
113 | 127 |
|
114 | 128 |
def model(weather_provider,which_day) |
115 | 129 |
if weather_provider == "wunderground" |
116 | 130 |
wunderground[which_day] |
117 |
- elsif weather_provider == "forecastio" |
|
118 |
- forecastio.each do |value| |
|
131 |
+ elsif weather_provider == "darksky" || weather_provider == "forecastio" |
|
132 |
+ dark_sky.each do |value| |
|
119 | 133 |
timestamp = Time.at(value.time) |
120 | 134 |
if (timestamp.to_date - Time.now.to_date).to_i == which_day |
121 | 135 |
day = { |
@@ -28,6 +28,7 @@ module Agents |
||
28 | 28 |
For example, "post,get" will enable POST and GET requests. Defaults |
29 | 29 |
to "post". |
30 | 30 |
* `response` - The response message to the request. Defaults to 'Event Created'. |
31 |
+ * `code` - The response code to the request. Defaults to '201'. |
|
31 | 32 |
* `recaptcha_secret` - Setting this to a reCAPTCHA "secret" key makes your agent verify incoming requests with reCAPTCHA. Don't forget to embed a reCAPTCHA snippet including your "site" key in the originating form(s). |
32 | 33 |
* `recaptcha_send_remote_addr` - Set this to true if your server is properly configured to set REMOTE_ADDR to the IP address of each visitor (instead of that of a proxy server). |
33 | 34 |
MD |
@@ -53,8 +54,16 @@ module Agents |
||
53 | 54 |
return ["Not Authorized", 401] unless secret == options['secret'] |
54 | 55 |
|
55 | 56 |
# check the verbs |
57 |
+<<<<<<< HEAD |
|
56 | 58 |
# verbs = (interpolated['verbs'] || 'post').split(/,/).map { |x| x.strip.downcase }.select { |x| x.present? } |
57 | 59 |
# return ["Please use #{verbs.join('/').upcase} requests only", 401] unless verbs.include?(method) |
60 |
+======= |
|
61 |
+ verbs = (interpolated['verbs'] || 'post').split(/,/).map { |x| x.strip.downcase }.select { |x| x.present? } |
|
62 |
+ return ["Please use #{verbs.join('/').upcase} requests only", 401] unless verbs.include?(method) |
|
63 |
+ |
|
64 |
+ # check the code |
|
65 |
+ code = (interpolated['code'].presence || 201).to_i |
|
66 |
+>>>>>>> 0fcd8e285ebe9c04fb6ce5abd5a1f776021bdf89 |
|
58 | 67 |
|
59 | 68 |
# check the reCAPTCHA response if required |
60 | 69 |
if recaptcha_secret = interpolated['recaptcha_secret'].presence |
@@ -86,7 +95,11 @@ module Agents |
||
86 | 95 |
create_event(payload: payload) |
87 | 96 |
end |
88 | 97 |
|
98 |
+<<<<<<< HEAD |
|
89 | 99 |
[response_message, 200] |
100 |
+======= |
|
101 |
+ [interpolated(params)['response'] || 'Event Created', code] |
|
102 |
+>>>>>>> 0fcd8e285ebe9c04fb6ce5abd5a1f776021bdf89 |
|
90 | 103 |
end |
91 | 104 |
|
92 | 105 |
def working? |
@@ -97,14 +110,14 @@ module Agents |
||
97 | 110 |
unless options['secret'].present? |
98 | 111 |
errors.add(:base, "Must specify a secret for 'Authenticating' requests") |
99 | 112 |
end |
113 |
+ |
|
114 |
+ if options['code'].present? && options['code'].to_s !~ /\A\s*(\d+|\{.*)\s*\z/ |
|
115 |
+ errors.add(:base, "Must specify a code for request responses") |
|
116 |
+ end |
|
100 | 117 |
end |
101 | 118 |
|
102 | 119 |
def payload_for(params) |
103 | 120 |
Utils.value_at(params, interpolated['payload_path']) || {} |
104 | 121 |
end |
105 |
- |
|
106 |
- def response_message |
|
107 |
- interpolated['response'] || 'Event Created' |
|
108 |
- end |
|
109 | 122 |
end |
110 | 123 |
end |
@@ -443,7 +443,7 @@ module Agents |
||
443 | 443 |
end |
444 | 444 |
|
445 | 445 |
def use_namespaces? |
446 |
- if value = interpolated.key?('use_namespaces') |
|
446 |
+ if interpolated.key?('use_namespaces') |
|
447 | 447 |
boolify(interpolated['use_namespaces']) |
448 | 448 |
else |
449 | 449 |
interpolated['extract'].none? { |name, extraction_details| |
@@ -1,15 +0,0 @@ |
||
1 |
-# Contacts are used only for the contact form on the Huginn website. If you host a public Huginn instance, you can use |
|
2 |
-# these to receive messages from visitors. |
|
3 |
- |
|
4 |
-class Contact < ActiveRecord::Base |
|
5 |
- attr_accessible :email, :message, :name |
|
6 |
- |
|
7 |
- validates_format_of :email, :with => /\A[A-Z0-9._%+-]+@[A-Z0-9.-]+\.(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|asia|jobs|museum)\Z/i |
|
8 |
- validates_presence_of :name, :message |
|
9 |
- |
|
10 |
- after_create :send_contact |
|
11 |
- |
|
12 |
- def send_contact |
|
13 |
- ContactMailer.send_contact(self).deliver |
|
14 |
- end |
|
15 |
-end |
@@ -1,7 +1,5 @@ |
||
1 | 1 |
# A ControlLink connects Agents in a control flow from the `controller` to the `control_target`. |
2 | 2 |
class ControlLink < ActiveRecord::Base |
3 |
- attr_accessible :controller_id, :target_id |
|
4 |
- |
|
5 | 3 |
belongs_to :controller, class_name: 'Agent', inverse_of: :control_links_as_controller |
6 | 4 |
belongs_to :control_target, class_name: 'Agent', inverse_of: :control_links_as_control_target |
7 | 5 |
end |
@@ -7,13 +7,11 @@ class Event < ActiveRecord::Base |
||
7 | 7 |
include JSONSerializedField |
8 | 8 |
include LiquidDroppable |
9 | 9 |
|
10 |
- attr_accessible :lat, :lng, :location, :payload, :user_id, :user, :expires_at |
|
11 |
- |
|
12 | 10 |
acts_as_mappable |
13 | 11 |
|
14 | 12 |
json_serialize :payload |
15 | 13 |
|
16 |
- belongs_to :user |
|
14 |
+ belongs_to :user, optional: true |
|
17 | 15 |
belongs_to :agent, :counter_cache => true |
18 | 16 |
|
19 | 17 |
has_many :agent_logs_as_inbound_event, :class_name => "AgentLog", :foreign_key => :inbound_event_id, :dependent => :nullify |
@@ -121,4 +119,8 @@ class EventDrop |
||
121 | 119 |
def _location_ |
122 | 120 |
@object.location |
123 | 121 |
end |
122 |
+ |
|
123 |
+ def as_json |
|
124 |
+ {location: _location_.as_json, agent: @object.agent.to_liquid.as_json, payload: @payload.as_json, created_at: created_at.as_json} |
|
125 |
+ end |
|
124 | 126 |
end |
@@ -1,7 +1,5 @@ |
||
1 | 1 |
# A Link connects Agents in a directed Event flow from the `source` to the `receiver`. |
2 | 2 |
class Link < ActiveRecord::Base |
3 |
- attr_accessible :source_id, :receiver_id |
|
4 |
- |
|
5 | 3 |
belongs_to :source, :class_name => "Agent", :inverse_of => :links_as_source |
6 | 4 |
belongs_to :receiver, :class_name => "Agent", :inverse_of => :links_as_receiver |
7 | 5 |
|
@@ -1,9 +1,6 @@ |
||
1 | 1 |
class Scenario < ActiveRecord::Base |
2 | 2 |
include HasGuid |
3 | 3 |
|
4 |
- attr_accessible :name, :agent_ids, :description, :public, :source_url, |
|
5 |
- :tag_fg_color, :tag_bg_color, :icon |
|
6 |
- |
|
7 | 4 |
belongs_to :user, :counter_cache => :scenario_count, :inverse_of => :scenarios |
8 | 5 |
has_many :scenario_memberships, :dependent => :destroy, :inverse_of => :scenario |
9 | 6 |
has_many :agents, :through => :scenario_memberships, :inverse_of => :scenarios |
@@ -1,6 +1,4 @@ |
||
1 | 1 |
class Service < ActiveRecord::Base |
2 |
- attr_accessible :provider, :name, :token, :secret, :refresh_token, :expires_at, :global, :options, :uid |
|
3 |
- |
|
4 | 2 |
serialize :options, Hash |
5 | 3 |
|
6 | 4 |
belongs_to :user, :inverse_of => :services |
@@ -57,17 +55,8 @@ class Service < ActiveRecord::Base |
||
57 | 55 |
(config = Devise.omniauth_configs[provider.to_sym]) && config.args[1] |
58 | 56 |
end |
59 | 57 |
|
60 |
- def self.provider_specific_options(omniauth) |
|
61 |
- case omniauth['provider'].to_sym |
|
62 |
- when :'37signals' |
|
63 |
- { user_id: omniauth['extra']['accounts'][0]['id'], name: omniauth['info']['name'] } |
|
64 |
- else |
|
65 |
- { name: omniauth['info']['nickname'] || omniauth['info']['name'] } |
|
66 |
- end |
|
67 |
- end |
|
68 |
- |
|
69 | 58 |
def self.initialize_or_update_via_omniauth(omniauth) |
70 |
- options = provider_specific_options(omniauth) |
|
59 |
+ options = get_options(omniauth) |
|
71 | 60 |
|
72 | 61 |
find_or_initialize_by(provider: omniauth['provider'], uid: omniauth['uid'].to_s).tap do |service| |
73 | 62 |
service.assign_attributes token: omniauth['credentials']['token'], |
@@ -78,4 +67,24 @@ class Service < ActiveRecord::Base |
||
78 | 67 |
options: options |
79 | 68 |
end |
80 | 69 |
end |
70 |
+ |
|
71 |
+ def self.register_options_provider(provider_name, &block) |
|
72 |
+ option_providers[provider_name] = block |
|
73 |
+ end |
|
74 |
+ |
|
75 |
+ def self.get_options(omniauth) |
|
76 |
+ option_providers.fetch(omniauth['provider'], option_providers['default']).call(omniauth) |
|
77 |
+ end |
|
78 |
+ |
|
79 |
+ private |
|
80 |
+ @@option_providers = HashWithIndifferentAccess.new |
|
81 |
+ cattr_reader :option_providers |
|
82 |
+ |
|
83 |
+ register_options_provider('default') do |omniauth| |
|
84 |
+ {name: omniauth['info']['nickname'] || omniauth['info']['name']} |
|
85 |
+ end |
|
86 |
+ |
|
87 |
+ register_options_provider('37signals') do |omniauth| |
|
88 |
+ {user_id: omniauth['extra']['accounts'][0]['id'], name: omniauth['info']['name']} |
|
89 |
+ end |
|
81 | 90 |
end |
@@ -12,11 +12,6 @@ class User < ActiveRecord::Base |
||
12 | 12 |
# This is in addition to a real persisted field like 'username' |
13 | 13 |
attr_accessor :login |
14 | 14 |
|
15 |
- ACCESSIBLE_ATTRIBUTES = [ :email, :username, :login, :password, :password_confirmation, :remember_me, :invitation_code ] |
|
16 |
- |
|
17 |
- attr_accessible *ACCESSIBLE_ATTRIBUTES |
|
18 |
- attr_accessible *(ACCESSIBLE_ATTRIBUTES + [:admin]), :as => :admin |
|
19 |
- |
|
20 | 15 |
validates_presence_of :username |
21 | 16 |
validates :username, uniqueness: { case_sensitive: false } |
22 | 17 |
validates_format_of :username, :with => /\A[a-zA-Z0-9_-]{3,15}\Z/, :message => "can only contain letters, numbers, underscores, and dashes, and must be between 3 and 15 characters in length." |
@@ -80,4 +75,19 @@ class User < ActiveRecord::Base |
||
80 | 75 |
def requires_no_invitation_code? |
81 | 76 |
!!@requires_no_invitation_code |
82 | 77 |
end |
78 |
+ |
|
79 |
+ def undefined_agent_types |
|
80 |
+ agents.reorder('').group(:type).pluck(:type).select do |type| |
|
81 |
+ begin |
|
82 |
+ type.constantize |
|
83 |
+ false |
|
84 |
+ rescue NameError |
|
85 |
+ true |
|
86 |
+ end |
|
87 |
+ end |
|
88 |
+ end |
|
89 |
+ |
|
90 |
+ def undefined_agents |
|
91 |
+ agents.where(type: undefined_agent_types).select('id, schedule, events_count, type as undefined') |
|
92 |
+ end |
|
83 | 93 |
end |
@@ -1,8 +1,6 @@ |
||
1 | 1 |
class UserCredential < ActiveRecord::Base |
2 | 2 |
MODES = %w[text java_script] |
3 | 3 |
|
4 |
- attr_accessible :credential_name, :credential_value, :mode |
|
5 |
- |
|
6 | 4 |
belongs_to :user |
7 | 5 |
|
8 | 6 |
validates_presence_of :credential_name |
@@ -23,7 +23,9 @@ class FormConfigurableAgentPresenter < Decorator |
||
23 | 23 |
@view.content_tag 'div' do |
24 | 24 |
@view.concat @view.text_area_tag("agent[options][#{attribute}]", value, html_options.merge(class: 'form-control', rows: 3)) |
25 | 25 |
if data[:ace].present? |
26 |
- @view.concat @view.content_tag('div', '', class: 'ace-editor', data: { source: "[name='agent[options][#{attribute}]']" }) |
|
26 |
+ ace_options = { source: "[name='agent[options][#{attribute}]']", mode: '', theme: ''}.deep_symbolize_keys! |
|
27 |
+ ace_options.deep_merge!(data[:ace].deep_symbolize_keys) if data[:ace].is_a?(Hash) |
|
28 |
+ @view.concat @view.content_tag('div', '', class: 'ace-editor', data: ace_options) |
|
27 | 29 |
end |
28 | 30 |
end |
29 | 31 |
when :boolean |
@@ -43,7 +45,7 @@ class FormConfigurableAgentPresenter < Decorator |
||
43 | 45 |
@view.concat(@view.text_field_tag "agent[options][#{attribute}]", value, html_options.merge(:class => "form-control #{@agent.send(:boolify, value) != nil ? 'hidden' : ''}")) |
44 | 46 |
end |
45 | 47 |
when :array, :string |
46 |
- @view.text_field_tag "agent[options][#{attribute}]", value, html_options.merge(:class => 'form-control') |
|
48 |
+ @view.text_field_tag "agent[options][#{attribute}]", value, html_options.deep_merge(:class => 'form-control', data: {cache_response: data[:cache_response] != false}) |
|
47 | 49 |
end |
48 | 50 |
end |
49 |
-end |
|
51 |
+end |
@@ -22,5 +22,8 @@ |
||
22 | 22 |
<div class="row"> |
23 | 23 |
<div class="col-md-12"> |
24 | 24 |
<%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, admin_users_path, class: "btn btn-default" %> |
25 |
+ <% if @user.persisted? %> |
|
26 |
+ <%= link_to 'Become User', switch_to_user_admin_user_path(@user), class: "btn btn-default btn-info", data: { confirm: 'This will log you in as another user. Would you like to continue?' } %> |
|
27 |
+ <% end %> |
|
25 | 28 |
</div> |
26 | 29 |
</div> |
@@ -24,12 +24,13 @@ |
||
24 | 24 |
<td><%= link_to user.username, edit_admin_user_path(user) %></td> |
25 | 25 |
<td><%= user.email %></td> |
26 | 26 |
<td><%= user_account_state(user) %></td> |
27 |
- <td><%= user.agents.active.count %></td> |
|
28 |
- <td><%= user.agents.inactive.count %></td> |
|
27 |
+ <td><%= link_to user.agents.active.count, switch_to_user_admin_user_path(user), data: { confirm: 'This will log you in as another user. Would you like to continue?' } %></td> |
|
28 |
+ <td><%= link_to user.agents.inactive.count, switch_to_user_admin_user_path(user), data: { confirm: 'This will log you in as another user. Would you like to continue?' } %></td> |
|
29 | 29 |
<td title='<%= user.created_at %>'><%= time_ago_in_words user.created_at %> ago</td> |
30 | 30 |
<td> |
31 | 31 |
<div class="btn-group btn-group-xs"> |
32 | 32 |
<% if user != current_user %> |
33 |
+ <%= link_to 'Become User', switch_to_user_admin_user_path(user), class: "btn btn-default", data: { confirm: 'This will log you in as another user. Would you like to continue?' } %> |
|
33 | 34 |
<% if user.active? %> |
34 | 35 |
<%= link_to 'Deactivate', deactivate_admin_user_path(user), method: :put, class: "btn btn-default" %> |
35 | 36 |
<% else %> |
@@ -25,7 +25,7 @@ |
||
25 | 25 |
<% if @agent.new_record? %> |
26 | 26 |
<div class="form-group type-select"> |
27 | 27 |
<%= f.label :type %> |
28 |
- <%= f.select :type, options_for_select([['Select an Agent Type', 'Agent', {title: ''}]] + Agent.types.map {|type| [type.name.gsub(/^.*::/, '').underscore.humanize.titleize, type, {title: h(Agent.build_for_type(type.name,current_user,{}).html_description.lines.first.strip)}] }, @agent.type), {}, :class => 'form-control' %> |
|
28 |
+ <%= f.select :type, options_for_select([['Select an Agent Type', 'Agent', {title: ''}]] + Agent.types.map {|type| [agent_type_to_human(type.name), type, {title: h(Agent.build_for_type(type.name,current_user,{}).html_description.lines.first.strip)}] }, @agent.type), {}, :class => 'form-control' %> |
|
29 | 29 |
</div> |
30 | 30 |
<% end %> |
31 | 31 |
</div> |
@@ -30,6 +30,13 @@ |
||
30 | 30 |
e.preventDefault(); |
31 | 31 |
var $form = $("#create-event-form"); |
32 | 32 |
var $status = $("#event-creation-status"); |
33 |
+ try{ |
|
34 |
+ JSON.parse($form.find("textarea").val()); |
|
35 |
+ } |
|
36 |
+ catch(err){ |
|
37 |
+ alert ('Sorry, there appears to be an error in your JSON input. Please fix it before continuing.'); |
|
38 |
+ return false; |
|
39 |
+ } |
|
33 | 40 |
$.ajax({ |
34 | 41 |
url: $form.attr('action'), |
35 | 42 |
method: "post", |
@@ -2,7 +2,7 @@ |
||
2 | 2 |
<div class='row'> |
3 | 3 |
<div class='col-md-12'> |
4 | 4 |
<div class="page-header"> |
5 |
- <h2>Your Agents</h2> |
|
5 |
+ <h2><%= session[:original_admin_user_id].present? ? "#{current_user.username}’s Agents" : 'Your Agents' %></h2> |
|
6 | 6 |
</div> |
7 | 7 |
|
8 | 8 |
<%= render 'agents/table' %> |
@@ -0,0 +1,44 @@ |
||
1 |
+<div class="container"> |
|
2 |
+ <div class='row'> |
|
3 |
+ <div class='col-md-12'> |
|
4 |
+ <div class="page-header"> |
|
5 |
+ <h3> |
|
6 |
+ <div class="alert alert-danger" role="alert"> |
|
7 |
+ Error: Agent(s) are 'missing in action' |
|
8 |
+ </div> |
|
9 |
+ </h3> |
|
10 |
+ </div> |
|
11 |
+ <blockquote> |
|
12 |
+ <p> |
|
13 |
+ You have one or more Agents registered in the database for which no corresponding definition is available in the source code: |
|
14 |
+ </p> |
|
15 |
+ <ul> |
|
16 |
+ <% @undefined_agent_types.each do |type| %> |
|
17 |
+ <li><%= agent_type_to_human(type) %></li> |
|
18 |
+ <% end %> |
|
19 |
+ </ul> |
|
20 |
+ <br/> |
|
21 |
+ <p> |
|
22 |
+ The issue most probably occurred because of one or more of the following reasons: |
|
23 |
+ </p> |
|
24 |
+ <ul> |
|
25 |
+ <li>If the respective Agent is distributed as a Ruby gem, it might have been removed from the <code>ADDITIONAL_GEMS</code> environment setting.</li> |
|
26 |
+ <li>If the respective Agent is distributed as part of the Huginn application codebase, it might have been removed from that either on purpose (because the Agent has been deprecated or been moved to an Agent gem) or accidentally. Please check if the Agent(s) in question are available in your Huginn codebase under the path <code>app/models/agents/</code>.</li> |
|
27 |
+ </ul> |
|
28 |
+ <br/> |
|
29 |
+ <p> |
|
30 |
+ You can fix the issue by adding the Agent(s) back to the application codebase by |
|
31 |
+ </p> |
|
32 |
+ <ul> |
|
33 |
+ <li>adding the respective Agent(s) to the the <code>ADDITIONAL_GEMS</code> environment setting. Please see <a href="https://github.com/cantino/huginn_agent" target="_blank">https://github.com/cantino/huginn_agent</a> for documentation on how to properly set it.</li> |
|
34 |
+ <li>adding the respective Agent(s) code to the Huginn application codebase (in case it was deleted accidentally).</li> |
|
35 |
+ <li>deleting the respective Agent(s) from the database using the button below.</li> |
|
36 |
+ </ul> |
|
37 |
+ <br/> |
|
38 |
+ <div class="btn-group"> |
|
39 |
+ <%= link_to icon_tag('glyphicon-trash') + ' Delete Missing Agents', undefined_agents_path, class: "btn btn-danger", method: :DELETE, data: { confirm: 'Are you sure all missing Agents should be deleted from the database?'} %> |
|
40 |
+ </div> |
|
41 |
+ </blockquote> |
|
42 |
+ </div> |
|
43 |
+ </div> |
|
44 |
+</div> |
@@ -60,9 +60,17 @@ |
||
60 | 60 |
<li class="dropdown"> |
61 | 61 |
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> |
62 | 62 |
Account |
63 |
+ <% if user_signed_in? && session[:original_admin_user_id].present? %> |
|
64 |
+ <span class="label label-warning"><%= current_user.username %></span> |
|
65 |
+ <% end %> |
|
63 | 66 |
<b class="caret"></b> |
64 | 67 |
</a> |
65 | 68 |
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel"> |
69 |
+ <% if user_signed_in? && session[:original_admin_user_id].present? %> |
|
70 |
+ <li> |
|
71 |
+ <%= link_to 'Switch Back to Admin User', switch_back_admin_users_path, tabindex: '-1' %> |
|
72 |
+ </li> |
|
73 |
+ <% end %> |
|
66 | 74 |
<li> |
67 | 75 |
<% if user_signed_in? %> |
68 | 76 |
<%= link_to 'Account', edit_user_registration_path, :tabindex => "-1" %> |
@@ -13,13 +13,13 @@ |
||
13 | 13 |
|
14 | 14 |
<td> |
15 | 15 |
<div class="btn-group btn-group-xs"> |
16 |
- <% if log.inbound_event_id.present? %> |
|
16 |
+ <% if log.inbound_event.present? %> |
|
17 | 17 |
<%= link_to 'Event In', event_path(log.inbound_event), class: "btn btn-default" %> |
18 | 18 |
<% else %> |
19 | 19 |
<%= link_to 'Event In', '#', class: "btn btn-default disabled" %> |
20 | 20 |
<% end %> |
21 | 21 |
|
22 |
- <% if log.outbound_event_id.present? %> |
|
22 |
+ <% if log.outbound_event.present? %> |
|
23 | 23 |
<%= link_to 'Event Out', event_path(log.outbound_event), class: "btn btn-default" %> |
24 | 24 |
<% else %> |
25 | 25 |
<%= link_to 'Event Out', '#', class: "btn btn-default disabled" %> |
@@ -31,4 +31,4 @@ |
||
31 | 31 |
</tr> |
32 | 32 |
<% end %> |
33 | 33 |
</table> |
34 |
-</div> |
|
34 |
+</div> |
@@ -1,8 +1,9 @@ |
||
1 | 1 |
#!/usr/bin/env ruby |
2 | 2 |
begin |
3 |
- load File.expand_path("../spring", __FILE__) |
|
4 |
-rescue LoadError |
|
3 |
+ load File.expand_path('../spring', __FILE__) |
|
4 |
+rescue LoadError => e |
|
5 |
+ raise unless e.message.include?('spring') |
|
5 | 6 |
end |
6 |
-APP_PATH = File.expand_path('../../config/application', __FILE__) |
|
7 |
+APP_PATH = File.expand_path('../config/application', __dir__) |
|
7 | 8 |
require_relative '../config/boot' |
8 | 9 |
require 'rails/commands' |
@@ -1,7 +1,8 @@ |
||
1 | 1 |
#!/usr/bin/env ruby |
2 | 2 |
begin |
3 |
- load File.expand_path("../spring", __FILE__) |
|
4 |
-rescue LoadError |
|
3 |
+ load File.expand_path('../spring', __FILE__) |
|
4 |
+rescue LoadError => e |
|
5 |
+ raise unless e.message.include?('spring') |
|
5 | 6 |
end |
6 | 7 |
require_relative '../config/boot' |
7 | 8 |
require 'rake' |
@@ -1,7 +1,8 @@ |
||
1 | 1 |
#!/usr/bin/env ruby |
2 | 2 |
begin |
3 |
- load File.expand_path("../spring", __FILE__) |
|
4 |
-rescue LoadError |
|
3 |
+ load File.expand_path('../spring', __FILE__) |
|
4 |
+rescue LoadError => e |
|
5 |
+ raise unless e.message.include?('spring') |
|
5 | 6 |
end |
6 | 7 |
require 'bundler/setup' |
7 | 8 |
load Gem.bin_path('rspec-core', 'rspec') |
@@ -4,12 +4,12 @@ |
||
4 | 4 |
# It gets overwritten when you run the `spring binstub` command. |
5 | 5 |
|
6 | 6 |
unless defined?(Spring) |
7 |
- require "rubygems" |
|
8 |
- require "bundler" |
|
7 |
+ require 'rubygems' |
|
8 |
+ require 'bundler' |
|
9 | 9 |
|
10 |
- if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m) |
|
11 |
- Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq } |
|
12 |
- gem "spring", match[1] |
|
13 |
- require "spring/binstub" |
|
10 |
+ if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) |
|
11 |
+ Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) } |
|
12 |
+ gem 'spring', match[1] |
|
13 |
+ require 'spring/binstub' |
|
14 | 14 |
end |
15 | 15 |
end |
@@ -1,5 +1,5 @@ |
||
1 | 1 |
# This file is used by Rack-based servers to start the application. |
2 | 2 |
|
3 |
-require ::File.expand_path('../config/environment', __FILE__) |
|
3 |
+require_relative 'config/environment' |
|
4 | 4 |
|
5 | 5 |
run Huginn::Application |
@@ -1,4 +1,4 @@ |
||
1 |
-require File.expand_path('../boot', __FILE__) |
|
1 |
+require_relative 'boot' |
|
2 | 2 |
|
3 | 3 |
require 'rails/all' |
4 | 4 |
|
@@ -39,15 +39,6 @@ module Huginn |
||
39 | 39 |
# like if you have constraints or database-specific column types |
40 | 40 |
# config.active_record.schema_format = :sql |
41 | 41 |
|
42 |
- # Enforce whitelist mode for mass assignment. |
|
43 |
- # This will create an empty whitelist of attributes available for mass-assignment for all models |
|
44 |
- # in your app. As such, your models will need to explicitly whitelist or blacklist accessible |
|
45 |
- # parameters by using an attr_accessible or attr_protected declaration. |
|
46 |
- config.active_record.whitelist_attributes = true |
|
47 |
- |
|
48 |
- # Do not swallow errors in after_commit/after_rollback callbacks. |
|
49 |
- config.active_record.raise_in_transactional_callbacks = true |
|
50 |
- |
|
51 | 42 |
config.active_job.queue_adapter = :delayed_job |
52 | 43 |
end |
53 | 44 |
end |
@@ -1,6 +1,6 @@ |
||
1 | 1 |
require 'rubygems' |
2 | 2 |
|
3 | 3 |
# Set up gems listed in the Gemfile. |
4 |
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) |
|
4 |
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) |
|
5 | 5 |
|
6 | 6 |
require 'bundler/setup' # Set up gems listed in the Gemfile. |
@@ -1,8 +1,5 @@ |
||
1 | 1 |
# Load the rails application |
2 |
-require File.expand_path('../application', __FILE__) |
|
3 |
- |
|
4 |
-# Remove the XML parser from the list that will be used to initialize the application's XML parser list. |
|
5 |
-ActionDispatch::ParamsParser::DEFAULT_PARSERS.delete(Mime::XML) |
|
2 |
+require_relative 'application' |
|
6 | 3 |
|
7 | 4 |
# Initialize the rails application |
8 | 5 |
Huginn::Application.initialize! |
@@ -16,9 +16,22 @@ Huginn::Application.configure do |
||
16 | 16 |
# Rake tasks automatically ignore this option for performance. |
17 | 17 |
config.eager_load = false |
18 | 18 |
|
19 |
- # Show full error reports and disable caching |
|
20 |
- config.consider_all_requests_local = true |
|
21 |
- config.action_controller.perform_caching = false |
|
19 |
+ # Show full error reports. |
|
20 |
+ config.consider_all_requests_local = true |
|
21 |
+ |
|
22 |
+ # Enable/disable caching. By default caching is disabled. |
|
23 |
+ if Rails.root.join('tmp/caching-dev.txt').exist? |
|
24 |
+ config.action_controller.perform_caching = true |
|
25 |
+ |
|
26 |
+ config.cache_store = :memory_store |
|
27 |
+ config.public_file_server.headers = { |
|
28 |
+ 'Cache-Control' => 'public, max-age=172800' |
|
29 |
+ } |
|
30 |
+ else |
|
31 |
+ config.action_controller.perform_caching = false |
|
32 |
+ |
|
33 |
+ config.cache_store = :null_store |
|
34 |
+ end |
|
22 | 35 |
|
23 | 36 |
# Print deprecation notices to the Rails logger |
24 | 37 |
config.active_support.deprecation = :log |
@@ -26,8 +39,8 @@ Huginn::Application.configure do |
||
26 | 39 |
# Only use best-standards-support built into browsers |
27 | 40 |
config.action_dispatch.best_standards_support = :builtin |
28 | 41 |
|
29 |
- # Raise exception on mass assignment protection for Active Record models |
|
30 |
- config.active_record.mass_assignment_sanitizer = :strict |
|
42 |
+ # Raise exception for unpermitted parameters |
|
43 |
+ config.action_controller.action_on_unpermitted_parameters = :raise |
|
31 | 44 |
|
32 | 45 |
# Raise an error on page load if there are pending migrations. |
33 | 46 |
config.active_record.migration_error = :page_load |
@@ -35,14 +48,8 @@ Huginn::Application.configure do |
||
35 | 48 |
# Expands the lines which load the assets |
36 | 49 |
config.assets.debug = true |
37 | 50 |
|
38 |
- # Asset digests allow you to set far-future HTTP expiration dates on all assets, |
|
39 |
- # yet still be able to expire them through the digest params. |
|
40 |
- config.assets.digest = true |
|
41 |
- |
|
42 |
- # Adds additional error checking when serving assets at runtime. |
|
43 |
- # Checks for improperly declared sprockets dependencies. |
|
44 |
- # Raises helpful error messages. |
|
45 |
- config.assets.raise_runtime_errors = true |
|
51 |
+ # Suppress logger output for asset requests. |
|
52 |
+ config.assets.quiet = true |
|
46 | 53 |
|
47 | 54 |
config.action_mailer.default_url_options = { :host => ENV['DOMAIN'] } |
48 | 55 |
config.action_mailer.asset_host = ENV['DOMAIN'] |
@@ -52,5 +59,10 @@ Huginn::Application.configure do |
||
52 | 59 |
else |
53 | 60 |
config.action_mailer.delivery_method = :letter_opener_web |
54 | 61 |
end |
62 |
+ config.action_mailer.perform_caching = false |
|
55 | 63 |
# smtp_settings moved to config/initializers/action_mailer.rb |
64 |
+ |
|
65 |
+ # Use an evented file watcher to asynchronously detect changes in source code, |
|
66 |
+ # routes, locales, etc. This feature depends on the listen gem. |
|
67 |
+ config.file_watcher = ActiveSupport::EventedFileUpdateChecker |
|
56 | 68 |
end |
@@ -14,15 +14,19 @@ Huginn::Application.configure do |
||
14 | 14 |
config.consider_all_requests_local = false |
15 | 15 |
config.action_controller.perform_caching = true |
16 | 16 |
|
17 |
- # Enable Rack::Cache to put a simple HTTP cache in front of your application |
|
18 |
- # Add `rack-cache` to your Gemfile before enabling this. |
|
19 |
- # For large-scale production use, consider using a caching reverse proxy like |
|
20 |
- # NGINX, varnish or squid. |
|
21 |
- # config.action_dispatch.rack_cache = true |
|
22 |
- |
|
23 | 17 |
# Disable serving static files from the `/public` folder by default since |
24 | 18 |
# Apache or NGINX already handles this. |
25 |
- config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? |
|
19 |
+ config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? |
|
20 |
+ |
|
21 |
+ if ENV["RAILS_LOG_TO_STDOUT"].present? || |
|
22 |
+ ENV['ON_HEROKU'] || |
|
23 |
+ ENV['HEROKU_POSTGRESQL_ROSE_URL'] || |
|
24 |
+ ENV['HEROKU_POSTGRESQL_GOLD_URL'] || |
|
25 |
+ File.read(File.join(File.dirname(__FILE__), '../../Procfile')) =~ /intended for Heroku/ |
|
26 |
+ logger = ActiveSupport::Logger.new(STDOUT) |
|
27 |
+ logger.formatter = config.log_formatter |
|
28 |
+ config.logger = ActiveSupport::TaggedLogging.new(logger) |
|
29 |
+ end |
|
26 | 30 |
|
27 | 31 |
# Compress JavaScripts and CSS |
28 | 32 |
config.assets.js_compressor = :uglifier |
@@ -31,15 +35,17 @@ Huginn::Application.configure do |
||
31 | 35 |
# Don't fallback to assets pipeline if a precompiled asset is missed |
32 | 36 |
config.assets.compile = false |
33 | 37 |
|
34 |
- # Generate digests for assets URLs |
|
35 |
- config.assets.digest = true |
|
36 |
- |
|
37 | 38 |
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb |
38 | 39 |
|
39 | 40 |
# Specifies the header that your server uses for sending files. |
40 | 41 |
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache |
41 | 42 |
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx |
42 | 43 |
|
44 |
+ # Mount Action Cable outside main process or domain |
|
45 |
+ # config.action_cable.mount_path = nil |
|
46 |
+ # config.action_cable.url = 'wss://example.com/cable' |
|
47 |
+ # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] |
|
48 |
+ |
|
43 | 49 |
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. |
44 | 50 |
config.force_ssl = ENV['FORCE_SSL'] == 'true' |
45 | 51 |
|
@@ -47,7 +53,7 @@ Huginn::Application.configure do |
||
47 | 53 |
config.log_level = :info |
48 | 54 |
|
49 | 55 |
# Prepend all log lines with the following tags |
50 |
- config.log_tags = [ :uuid ] # :subdomain |
|
56 |
+ config.log_tags = [ :request_id ] # :subdomain |
|
51 | 57 |
|
52 | 58 |
# Use a different logger for distributed setups |
53 | 59 |
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) |
@@ -79,11 +85,12 @@ Huginn::Application.configure do |
||
79 | 85 |
|
80 | 86 |
config.action_mailer.default_url_options = { :host => ENV['DOMAIN'] } |
81 | 87 |
config.action_mailer.asset_host = ENV['DOMAIN'] |
82 |
- if ENV['ASSET_HOST'] |
|
88 |
+ if ENV['ASSET_HOST'].present? |
|
83 | 89 |
config.action_mailer.asset_host = ENV['ASSET_HOST'] |
84 | 90 |
end |
85 | 91 |
config.action_mailer.perform_deliveries = true |
86 | 92 |
config.action_mailer.raise_delivery_errors = true |
87 | 93 |
config.action_mailer.delivery_method = :smtp |
94 |
+ config.action_mailer.perform_caching = false |
|
88 | 95 |
# smtp_settings moved to config/initializers/action_mailer.rb |
89 | 96 |
end |
@@ -13,8 +13,10 @@ Huginn::Application.configure do |
||
13 | 13 |
config.eager_load = false |
14 | 14 |
|
15 | 15 |
# Configure static asset server for tests with Cache-Control for performance |
16 |
- config.serve_static_files = true |
|
17 |
- config.static_cache_control = "public, max-age=3600" |
|
16 |
+ config.public_file_server.enabled = true |
|
17 |
+ config.public_file_server.headers = { |
|
18 |
+ 'Cache-Control' => 'public, max-age=3600' |
|
19 |
+ } |
|
18 | 20 |
|
19 | 21 |
# Show full error reports and disable caching |
20 | 22 |
config.consider_all_requests_local = true |
@@ -25,6 +27,7 @@ Huginn::Application.configure do |
||
25 | 27 |
|
26 | 28 |
# Disable request forgery protection in test environment |
27 | 29 |
config.action_controller.allow_forgery_protection = false |
30 |
+ config.action_mailer.perform_caching = false |
|
28 | 31 |
|
29 | 32 |
# Tell Action Mailer not to deliver emails to the real world. |
30 | 33 |
# The :test delivery method accumulates sent emails in the |
@@ -33,11 +36,8 @@ Huginn::Application.configure do |
||
33 | 36 |
|
34 | 37 |
config.action_mailer.raise_delivery_errors = true |
35 | 38 |
|
36 |
- # Raise exception on mass assignment protection for Active Record models |
|
37 |
- config.active_record.mass_assignment_sanitizer = :strict |
|
38 |
- |
|
39 |
- # Randomize the order test cases are executed. |
|
40 |
- config.active_support.test_order = :random |
|
39 |
+ # Raise exception for unpermitted parameters |
|
40 |
+ config.action_controller.action_on_unpermitted_parameters = :raise |
|
41 | 41 |
|
42 | 42 |
# Print deprecation notices to the stderr |
43 | 43 |
config.active_support.deprecation = :stderr |
@@ -4,8 +4,8 @@ ActionMailer::Base.smtp_settings = { |
||
4 | 4 |
domain: ENV['SMTP_DOMAIN'], |
5 | 5 |
authentication: ENV['SMTP_AUTHENTICATION'] || "plain", |
6 | 6 |
enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] == 'true', |
7 |
- user_name: ENV['SMTP_USER_NAME'] || "", |
|
8 |
- password: ENV['SMTP_PASSWORD'] || "", |
|
7 |
+ user_name: ENV['SMTP_USER_NAME'].presence, |
|
8 |
+ password: ENV['SMTP_PASSWORD'].presence, |
|
9 | 9 |
openssl_verify_mode: ENV['SMTP_OPENSSL_VERIFY_MODE'].presence, |
10 | 10 |
ca_path: ENV['SMTP_OPENSSL_CA_PATH'].presence, |
11 | 11 |
ca_file: ENV['SMTP_OPENSSL_CA_FILE'].presence |
@@ -1 +0,0 @@ |
||
1 |
-require 'ar_mysql_column_charset' |
@@ -0,0 +1,5 @@ |
||
1 |
+# Be sure to restart your server when you modify this file. |
|
2 |
+ |
|
3 |
+# Specify a serializer for the signed and encrypted cookie jars. |
|
4 |
+# Valid options are :json, :marshal, and :hybrid. |
|
5 |
+Rails.application.config.action_dispatch.cookies_serializer = :hybrid |
@@ -10,18 +10,18 @@ Delayed::Worker.logger = Rails.logger |
||
10 | 10 |
# Delayed::Worker.logger = Logger.new(Rails.root.join('log', 'delayed_job.log')) |
11 | 11 |
# Delayed::Worker.logger.level = Logger::DEBUG |
12 | 12 |
|
13 |
-class Delayed::Job |
|
14 |
- scope :pending, -> { where("locked_at IS NULL AND attempts = 0") } |
|
15 |
- scope :awaiting_retry, -> { where("failed_at IS NULL AND attempts > 0 AND locked_at IS NULL") } |
|
16 |
- scope :failed, -> { where("failed_at IS NOT NULL") } |
|
17 |
-end |
|
13 |
+ActiveSupport.on_load(:delayed_job_active_record) do |
|
14 |
+ class Delayed::Job |
|
15 |
+ scope :pending, -> { where("locked_at IS NULL AND attempts = 0") } |
|
16 |
+ scope :awaiting_retry, -> { where("failed_at IS NULL AND attempts > 0 AND locked_at IS NULL") } |
|
17 |
+ scope :failed, -> { where("failed_at IS NOT NULL") } |
|
18 |
+ end |
|
18 | 19 |
|
19 |
-def database_deadlocks_when_using_optimized_strategy? |
|
20 |
- ENV["DATABASE_ADAPTER"] == "mysql2" |
|
21 |
-end |
|
20 |
+ database_deadlocks_when_using_optimized_strategy = lambda do |
|
21 |
+ ENV["DATABASE_ADAPTER"] == "mysql2" |
|
22 |
+ end |
|
22 | 23 |
|
23 |
-if database_deadlocks_when_using_optimized_strategy? |
|
24 | 24 |
Delayed::Backend::ActiveRecord.configure do |config| |
25 | 25 |
config.reserve_sql_strategy = :default_sql |
26 |
- end |
|
26 |
+ end if database_deadlocks_when_using_optimized_strategy.call |
|
27 | 27 |
end |
@@ -0,0 +1,24 @@ |
||
1 |
+# Be sure to restart your server when you modify this file. |
|
2 |
+# |
|
3 |
+# This file contains migration options to ease your Rails 5.0 upgrade. |
|
4 |
+# |
|
5 |
+# Read the Rails 5.0 release notes for more info on each option. |
|
6 |
+ |
|
7 |
+# Enable per-form CSRF tokens. Previous versions had false. |
|
8 |
+Rails.application.config.action_controller.per_form_csrf_tokens = true |
|
9 |
+ |
|
10 |
+# Enable origin-checking CSRF mitigation. Previous versions had false. |
|
11 |
+Rails.application.config.action_controller.forgery_protection_origin_check = true |
|
12 |
+ |
|
13 |
+# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. |
|
14 |
+# Previous versions had false. |
|
15 |
+ActiveSupport.to_time_preserves_timezone = true |
|
16 |
+ |
|
17 |
+# Require `belongs_to` associations by default. Previous versions had false. |
|
18 |
+Rails.application.config.active_record.belongs_to_required_by_default = true |
|
19 |
+ |
|
20 |
+# Do not halt callback chains when a callback returns false. Previous versions had true. |
|
21 |
+ActiveSupport.halt_callback_chains_on_return_false = false |
|
22 |
+ |
|
23 |
+# Configure SSL options to enable HSTS with subdomains. Previous versions had false. |
|
24 |
+Rails.application.config.ssl_options = { hsts: { subdomains: true } } |
@@ -1,2 +1,2 @@ |
||
1 | 1 |
ActionView::Base.sanitized_allowed_tags += Set.new(%w(style table thead tbody tr th td)) |
2 |
-ActionView::Base.sanitized_allowed_attributes += Set.new(%w(border cellspacing cellpadding valign)) |
|
2 |
+ActionView::Base.sanitized_allowed_attributes += Set.new(%w(border cellspacing cellpadding valign style)) |
@@ -1,10 +1,9 @@ |
||
1 |
-Rails::Rack::Logger.class_eval do |
|
2 |
- def call_with_silence_worker_status(env) |
|
3 |
- previous_level = Rails.logger.level |
|
4 |
- Rails.logger.level = Logger::ERROR if env['PATH_INFO'] =~ %r{^/worker_status} |
|
5 |
- call_without_silence_worker_status(env) |
|
6 |
- ensure |
|
7 |
- Rails.logger.level = previous_level |
|
1 |
+module SilencedLogger |
|
2 |
+ def call(env) |
|
3 |
+ return super(env) if env['PATH_INFO'] !~ %r{^/worker_status} |
|
4 |
+ Rails.logger.silence(Logger::ERROR) do |
|
5 |
+ super(env) |
|
6 |
+ end |
|
8 | 7 |
end |
9 |
- alias_method_chain :call, :silence_worker_status |
|
10 | 8 |
end |
9 |
+Rails::Rack::Logger.send(:prepend, SilencedLogger) |
@@ -5,7 +5,7 @@ |
||
5 | 5 |
|
6 | 6 |
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. |
7 | 7 |
ActiveSupport.on_load(:action_controller) do |
8 |
- wrap_parameters format: [:json] if respond_to?(:wrap_parameters) |
|
8 |
+ wrap_parameters format: [:json] |
|
9 | 9 |
end |
10 | 10 |
|
11 | 11 |
# To enable root element in JSON for ActiveRecord objects. |
@@ -15,6 +15,7 @@ Huginn::Application.routes.draw do |
||
15 | 15 |
get :event_descriptions |
16 | 16 |
post :validate |
17 | 17 |
post :complete |
18 |
+ delete :undefined, action: :destroy_undefined |
|
18 | 19 |
end |
19 | 20 |
|
20 | 21 |
resources :logs, :only => [:index] do |
@@ -84,6 +85,10 @@ Huginn::Application.routes.draw do |
||
84 | 85 |
member do |
85 | 86 |
put :deactivate |
86 | 87 |
put :activate |
88 |
+ get :switch_to_user |
|
89 |
+ end |
|
90 |
+ collection do |
|
91 |
+ get :switch_back |
|
87 | 92 |
end |
88 | 93 |
end |
89 | 94 |
end |
@@ -0,0 +1,6 @@ |
||
1 |
+%w( |
|
2 |
+ .ruby-version |
|
3 |
+ .rbenv-vars |
|
4 |
+ tmp/restart.txt |
|
5 |
+ tmp/caching-dev.txt |
|
6 |
+).each { |path| Spring.watch(path) } |
@@ -1,3 +1,5 @@ |
||
1 |
+require 'liquid_migrator' |
|
2 |
+ |
|
1 | 3 |
class MigrateAgentsToLiquidTemplating < ActiveRecord::Migration |
2 | 4 |
class Agent < ActiveRecord::Base |
3 | 5 |
include JSONSerializedField |
@@ -3,7 +3,6 @@ class SetCharsetForMysql < ActiveRecord::Migration |
||
3 | 3 |
@all_models ||= [ |
4 | 4 |
Agent, |
5 | 5 |
AgentLog, |
6 |
- Contact, |
|
7 | 6 |
Event, |
8 | 7 |
Link, |
9 | 8 |
Scenario, |
@@ -23,7 +22,6 @@ class SetCharsetForMysql < ActiveRecord::Migration |
||
23 | 22 |
all_models.each { |model| |
24 | 23 |
table_name = model.table_name |
25 | 24 |
|
26 |
- # `contacts` may not exist |
|
27 | 25 |
next unless connection.table_exists? table_name |
28 | 26 |
|
29 | 27 |
model.columns.each { |column| |
@@ -0,0 +1,25 @@ |
||
1 |
+class ChangeEventsOrderToEventsListOrder < ActiveRecord::Migration |
|
2 |
+ def up |
|
3 |
+ Agents::DataOutputAgent.find_each do |agent| |
|
4 |
+ if value = agent.options.delete('events_order') |
|
5 |
+ agent.options['events_list_order'] = value |
|
6 |
+ agent.save!(validate: false) |
|
7 |
+ end |
|
8 |
+ end |
|
9 |
+ end |
|
10 |
+ |
|
11 |
+ def down |
|
12 |
+ Agents::DataOutputAgent.transaction do |
|
13 |
+ Agents::DataOutputAgent.find_each do |agent| |
|
14 |
+ if agent.options['events_order'] |
|
15 |
+ raise ActiveRecord::IrreversibleMigration, "Cannot revert migration because events_order is configured" |
|
16 |
+ end |
|
17 |
+ |
|
18 |
+ if value = agent.options.delete('events_list_order') |
|
19 |
+ agent.options['events_order'] = value |
|
20 |
+ agent.save!(validate: false) |
|
21 |
+ end |
|
22 |
+ end |
|
23 |
+ end |
|
24 |
+ end |
|
25 |
+end |
@@ -0,0 +1,8 @@ |
||
1 |
+class RemoveQueueFromEmailDigestAgentMemory < ActiveRecord::Migration |
|
2 |
+ def up |
|
3 |
+ Agents::EmailDigestAgent.find_each do |agent| |
|
4 |
+ agent.memory.delete("queue") |
|
5 |
+ agent.save!(validate: false) |
|
6 |
+ end |
|
7 |
+ end |
|
8 |
+end |
@@ -0,0 +1,15 @@ |
||
1 |
+class SetEmitErrorEventForTwitterActionAgents < ActiveRecord::Migration |
|
2 |
+ def up |
|
3 |
+ Agents::TwitterActionAgent.find_each do |agent| |
|
4 |
+ agent.options['emit_error_events'] = 'true' |
|
5 |
+ agent.save!(validate: false) |
|
6 |
+ end |
|
7 |
+ end |
|
8 |
+ |
|
9 |
+ def down |
|
10 |
+ Agents::TwitterActionAgent.find_each do |agent| |
|
11 |
+ agent.options.delete('emit_error_events') |
|
12 |
+ agent.save!(validate: false) |
|
13 |
+ end |
|
14 |
+ end |
|
15 |
+end |
@@ -0,0 +1,58 @@ |
||
1 |
+class UpdatePushoverAgentOptions < ActiveRecord::Migration |
|
2 |
+ DEFAULT_OPTIONS = { |
|
3 |
+ 'message' => '{{ message | default: text }}', |
|
4 |
+ 'device' => '{{ device }}', |
|
5 |
+ 'title' => '{{ title | default: subject }}', |
|
6 |
+ 'url' => '{{ url }}', |
|
7 |
+ 'url_title' => '{{ url_title }}', |
|
8 |
+ 'priority' => '{{ priority }}', |
|
9 |
+ 'timestamp' => '{{ timestamp }}', |
|
10 |
+ 'sound' => '{{ sound }}', |
|
11 |
+ 'retry' => '{{ retry }}', |
|
12 |
+ 'expire' => '{{ expire }}', |
|
13 |
+ } |
|
14 |
+ |
|
15 |
+ def up |
|
16 |
+ Agents::PushoverAgent.find_each do |agent| |
|
17 |
+ options = agent.options |
|
18 |
+ DEFAULT_OPTIONS.each_pair do |key, default| |
|
19 |
+ current = options[key] |
|
20 |
+ |
|
21 |
+ options[key] = |
|
22 |
+ if current.blank? |
|
23 |
+ default |
|
24 |
+ else |
|
25 |
+ "#{prefix_for(key)}#{current}#{suffix_for(key)}" |
|
26 |
+ end |
|
27 |
+ end |
|
28 |
+ agent.save!(validate: false) |
|
29 |
+ end |
|
30 |
+ end |
|
31 |
+ |
|
32 |
+ def down |
|
33 |
+ Agents::PushoverAgent.transaction do |
|
34 |
+ Agents::PushoverAgent.find_each do |agent| |
|
35 |
+ options = agent.options |
|
36 |
+ DEFAULT_OPTIONS.each_pair do |key, default| |
|
37 |
+ current = options[key] |
|
38 |
+ |
|
39 |
+ options[key] = |
|
40 |
+ if current == default |
|
41 |
+ '' |
|
42 |
+ else |
|
43 |
+ current[/\A#{Regexp.quote(prefix_for(key))}(.*)#{Regexp.quote(suffix_for(key))}\z/, 1] |
|
44 |
+ end or raise ActiveRecord::IrreversibleMigration, "Cannot revert migration once Pushover agents are configured" |
|
45 |
+ end |
|
46 |
+ agent.save!(validate: false) |
|
47 |
+ end |
|
48 |
+ end |
|
49 |
+ end |
|
50 |
+ |
|
51 |
+ def prefix_for(key) |
|
52 |
+ "{% capture _default_ %}" |
|
53 |
+ end |
|
54 |
+ |
|
55 |
+ def suffix_for(key) |
|
56 |
+ "{% endcapture %}" << DEFAULT_OPTIONS[key].sub(/(?=\}\}\z)/, '| default: _default_ ') |
|
57 |
+ end |
|
58 |
+end |
@@ -0,0 +1,9 @@ |
||
1 |
+class ResetDataOutputAgents < ActiveRecord::Migration |
|
2 |
+ def up |
|
3 |
+ Agents::DataOutputAgent.find_each do |agent| |
|
4 |
+ agent.memory = {} |
|
5 |
+ agent.save(validate: false) |
|
6 |
+ agent.latest_events(true) |
|
7 |
+ end |
|
8 |
+ end |
|
9 |
+end |
@@ -1,14 +1,14 @@ |
||
1 | 1 |
class Seeder |
2 | 2 |
def self.seed |
3 |
- user = User.find_or_initialize_by(:email => ENV['SEED_EMAIL'] || "admin@example.com") |
|
3 |
+ user = User.find_or_initialize_by(:email => ENV['SEED_EMAIL'].presence || "admin@example.com") |
|
4 | 4 |
if user.persisted? |
5 | 5 |
puts "User with email '#{user.email}' already exists, not seeding." |
6 | 6 |
exit |
7 | 7 |
end |
8 | 8 |
|
9 |
- user.username = ENV['SEED_USERNAME'] || "admin" |
|
10 |
- user.password = ENV['SEED_PASSWORD'] || "password" |
|
11 |
- user.password_confirmation = ENV['SEED_PASSWORD'] || "password" |
|
9 |
+ user.username = ENV['SEED_USERNAME'].presence || "admin" |
|
10 |
+ user.password = ENV['SEED_PASSWORD'].presence || "password" |
|
11 |
+ user.password_confirmation = ENV['SEED_PASSWORD'].presence || "password" |
|
12 | 12 |
user.invitation_code = User::INVITATION_CODES.first |
13 | 13 |
user.admin = true |
14 | 14 |
user.save! |
@@ -8,7 +8,7 @@ |
||
8 | 8 |
|
9 | 9 |
### Manual installation |
10 | 10 |
|
11 |
-Manual installation instructions which will guide through the steps to install Huginn on any Ubuntu 12.04/14.04 or Debian 6/7 server. |
|
11 |
+Manual installation instructions which will guide through the steps to install Huginn on any Ubuntu 12.04/14.04/16.04 or Debian 6/7 server. |
|
12 | 12 |
|
13 | 13 |
- [Install](manual/README.md) Requirements, directory structures and installation from source. |
14 | 14 |
- [Update](manual/update.md) Update your installation. |
@@ -17,4 +17,4 @@ Manual installation instructions which will guide through the steps to install H |
||
17 | 17 |
### Heroku |
18 | 18 |
|
19 | 19 |
- [Deploy to Heroku](heroku/install.md) |
20 |
-- [Update](heroku/update.md) an existing Heroku deployment |
|
20 |
+- [Update](heroku/update.md) an existing Heroku deployment |
@@ -68,7 +68,7 @@ namespace :foreman do |
||
68 | 68 |
end |
69 | 69 |
|
70 | 70 |
# If you want to use rvm on your server and have it maintained by Capistrano, uncomment these lines: |
71 |
-# set :rvm_ruby_string, '2.0.0@huginn' |
|
71 |
+# set :rvm_ruby_string, '2.3.1@huginn' |
|
72 | 72 |
# set :rvm_type, :user |
73 | 73 |
# before 'deploy', 'rvm:install_rvm' |
74 | 74 |
# before 'deploy', 'rvm:install_ruby' |
@@ -65,8 +65,8 @@ Remove the old Ruby versions if present: |
||
65 | 65 |
Download Ruby and compile it: |
66 | 66 |
|
67 | 67 |
mkdir /tmp/ruby && cd /tmp/ruby |
68 |
- curl -L --progress http://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.0.tar.bz2 | tar xj |
|
69 |
- cd ruby-2.3.0 |
|
68 |
+ curl -L --progress http://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.bz2 | tar xj |
|
69 |
+ cd ruby-2.3.1 |
|
70 | 70 |
./configure --disable-install-rdoc |
71 | 71 |
make -j`nproc` |
72 | 72 |
sudo make install |
@@ -110,7 +110,7 @@ Create a user for Huginn do not type the `mysql>`, this is part of the prompt. C |
||
110 | 110 |
|
111 | 111 |
Ensure you can use the InnoDB engine which is necessary to support long indexes |
112 | 112 |
|
113 |
- mysql> SET storage_engine=INNODB; |
|
113 |
+ mysql> SET default_storage_engine=INNODB; |
|
114 | 114 |
|
115 | 115 |
# If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) |
116 | 116 |
# for the setting "innodb = off" |
@@ -134,7 +134,7 @@ You should now see `ERROR 1049 (42000): Unknown database 'huginn_production'` wh |
||
134 | 134 |
You are done installing the database and can go back to the rest of the installation. |
135 | 135 |
|
136 | 136 |
|
137 |
-## 6. Huginn |
|
137 |
+## 5. Huginn |
|
138 | 138 |
|
139 | 139 |
### Clone the Source |
140 | 140 |
|
@@ -264,7 +264,7 @@ Export the init scripts: |
||
264 | 264 |
|
265 | 265 |
sudo bundle exec rake production:status |
266 | 266 |
|
267 |
-## 7. Nginx |
|
267 |
+## 6. Nginx |
|
268 | 268 |
|
269 | 269 |
**Note:** Nginx is the officially supported web server for Huginn. If you cannot or do not want to use Nginx as your web server, the wiki has a page on how to configure [apache](https://github.com/cantino/huginn/wiki/Apache-Huginn-configuration). |
270 | 270 |
|
@@ -4,7 +4,7 @@ |
||
4 | 4 |
|
5 | 5 |
### Supported Unix distributions by this guide |
6 | 6 |
|
7 |
-- Ubuntu (14.04 and 12.04) |
|
7 |
+- Ubuntu (16.04, 14.04 and 12.04) |
|
8 | 8 |
- Debian (Jessie and Wheezy) |
9 | 9 |
|
10 | 10 |
### Unsupported Unix distributions |
@@ -27,7 +27,7 @@ Please consider using a virtual machine to run Huginn on Windows. |
||
27 | 27 |
|
28 | 28 |
## Ruby versions |
29 | 29 |
|
30 |
-Huginn requires Ruby (MRI) 2.0, 2.1 or 2.2 |
|
30 |
+Huginn requires Ruby (MRI) 2.2 or 2.3. |
|
31 | 31 |
You will have to use the standard MRI implementation of Ruby. |
32 | 32 |
We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but Huginn needs several Gems that have native extensions. |
33 | 33 |
|
@@ -65,4 +65,4 @@ A DelayedJob worker is a separate process which runs your Huginn Agents. It fetc |
||
65 | 65 |
|
66 | 66 |
Estimating the amount of workers needed is easy. One worker can perform just one check at a time. |
67 | 67 |
If you have 60 Agents checking websites every minute which take about 1 second to respond, one worker is fine. |
68 |
-If you need more Agents or are dealing with slow/unreliable websites/services, you should consider running additional workers. |
|
68 |
+If you need more Agents or are dealing with slow/unreliable websites/services, you should consider running additional workers. |
@@ -43,7 +43,7 @@ grep = /app/.env.example | sed -e 's/^#\([^ ]\)/\1/' | grep -v -e '^#' | \ |
||
43 | 43 |
|
44 | 44 |
eval "echo RAILS_ENV=${RAILS_ENV}" >> .env |
45 | 45 |
eval "echo START_MYSQL=${START_MYSQL}" >> .env |
46 |
-echo "ON_HEROKU=true" >> .env |
|
46 |
+echo "RAILS_LOG_TO_STDOUT=true" >> .env |
|
47 | 47 |
echo "RAILS_SERVE_STATIC_FILES=true" >> .env |
48 | 48 |
|
49 | 49 |
chmod ugo+r /app/.env |
@@ -27,7 +27,7 @@ $minimal_apt_get_install build-essential checkinstall git-core \ |
||
27 | 27 |
libncurses5-dev libffi-dev libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev \ |
28 | 28 |
graphviz libgraphviz-dev \ |
29 | 29 |
libmysqlclient-dev libpq-dev libsqlite3-dev \ |
30 |
- ruby2.2 ruby2.2-dev |
|
30 |
+ ruby2.3 ruby2.3-dev |
|
31 | 31 |
locale-gen en_US.UTF-8 |
32 | 32 |
update-locale LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 |
33 | 33 |
gem install --no-ri --no-rdoc bundler |
@@ -27,3 +27,16 @@ mv config/unicorn.rb.example config/unicorn.rb |
||
27 | 27 |
sed -ri 's/^listen .*$/listen ENV["PORT"]/' config/unicorn.rb |
28 | 28 |
sed -ri 's/^stderr_path.*$//' config/unicorn.rb |
29 | 29 |
sed -ri 's/^stdout_path.*$//' config/unicorn.rb |
30 |
+ |
|
31 |
+# Add ENV variables to .env.example which are not present in it but usable |
|
32 |
+cat >> /app/.env.example <<EOF |
|
33 |
+ASSET_HOST= |
|
34 |
+DEFAULT_SCENARIO_FILE= |
|
35 |
+RAILS_SERVE_STATIC_FILES= |
|
36 |
+SEED_EMAIL= |
|
37 |
+SEED_PASSWORD= |
|
38 |
+SEED_USERNAME= |
|
39 |
+SMTP_OPENSSL_CA_FILE= |
|
40 |
+SMTP_OPENSSL_CA_PATH= |
|
41 |
+SMTP_OPENSSL_VERIFY_MODE= |
|
42 |
+EOF |
@@ -1,35 +1,45 @@ |
||
1 |
-mysqldata: |
|
2 |
- image: mysql:5.7 |
|
3 |
- command: /bin/true |
|
1 |
+# This needs at least compose 1.6.0 |
|
2 |
+version: '2' |
|
4 | 3 |
|
5 |
-mysql: |
|
6 |
- image: mysql:5.7 |
|
7 |
- volumes_from: |
|
8 |
- - mysqldata |
|
9 |
- environment: |
|
10 |
- MYSQL_ROOT_PASSWORD: myrootpassword |
|
11 |
- MYSQL_DATABASE: huginn |
|
12 |
- MYSQL_USER: huginn |
|
13 |
- MYSQL_PASSWORD: myhuginnpassword |
|
4 |
+services: |
|
5 |
+ mysqldata: |
|
6 |
+ image: mysql:5.7 |
|
7 |
+ command: /bin/true |
|
14 | 8 |
|
15 |
-huginn_web: |
|
16 |
- image: cantino/huginn-single-process |
|
17 |
- restart: always |
|
18 |
- extends: |
|
19 |
- file: environment.yml |
|
20 |
- service: huginn_base |
|
21 |
- ports: |
|
22 |
- - 3000:3000 |
|
23 |
- links: |
|
24 |
- - mysql |
|
9 |
+ mysql: |
|
10 |
+ image: mysql:5.7 |
|
11 |
+ volumes_from: |
|
12 |
+ - mysqldata |
|
13 |
+ environment: |
|
14 |
+ MYSQL_ROOT_PASSWORD: myrootpassword |
|
15 |
+ MYSQL_DATABASE: huginn |
|
16 |
+ MYSQL_USER: huginn |
|
17 |
+ MYSQL_PASSWORD: myhuginnpassword |
|
25 | 18 |
|
26 |
-huginn_threaded: |
|
27 |
- image: cantino/huginn-single-process |
|
28 |
- restart: always |
|
29 |
- extends: |
|
30 |
- file: environment.yml |
|
31 |
- service: huginn_base |
|
32 |
- links: |
|
33 |
- - mysql |
|
34 |
- command: /scripts/init bin/threaded.rb |
|
19 |
+ huginn_web: |
|
20 |
+ image: cantino/huginn-single-process |
|
21 |
+ restart: always |
|
22 |
+ extends: |
|
23 |
+ file: environment.yml |
|
24 |
+ service: huginn_base |
|
25 |
+ ports: |
|
26 |
+ - 3000:3000 |
|
27 |
+ links: |
|
28 |
+ - mysql |
|
29 |
+ environment: |
|
30 |
+ MYSQL_PORT_3306_TCP_ADDR: mysql |
|
31 |
+ MYSQL_PORT_3306_TCP_PORT: 3306 |
|
32 |
+ |
|
33 |
+ huginn_threaded: |
|
34 |
+ image: cantino/huginn-single-process |
|
35 |
+ restart: always |
|
36 |
+ extends: |
|
37 |
+ file: environment.yml |
|
38 |
+ service: huginn_base |
|
39 |
+ links: |
|
40 |
+ - mysql |
|
41 |
+ command: /scripts/init bin/threaded.rb |
|
42 |
+ environment: |
|
43 |
+ MYSQL_PORT_3306_TCP_ADDR: mysql |
|
44 |
+ MYSQL_PORT_3306_TCP_PORT: 3306 |
|
35 | 45 |
|
@@ -1,7 +1,11 @@ |
||
1 |
-huginn_base: |
|
2 |
- environment: |
|
3 |
- DATABASE_ADAPTER: mysql2 |
|
4 |
- DATABASE_NAME: huginn |
|
5 |
- DATABASE_USERNAME: huginn |
|
6 |
- DATABASE_PASSWORD: myhuginnpassword |
|
7 |
- APP_SECRET_TOKEN: 3bd139f9186b31a85336bb89cd1a1337078921134b2f48e022fd09c234d764d3e19b018b2ab789c6e0e04a1ac9e3365116368049660234c2038dc9990513d49c |
|
1 |
+# This needs at least compose 1.6.0 |
|
2 |
+version: '2' |
|
3 |
+ |
|
4 |
+services: |
|
5 |
+ huginn_base: |
|
6 |
+ environment: |
|
7 |
+ DATABASE_ADAPTER: mysql2 |
|
8 |
+ DATABASE_NAME: huginn |
|
9 |
+ DATABASE_USERNAME: huginn |
|
10 |
+ DATABASE_PASSWORD: myhuginnpassword |
|
11 |
+ APP_SECRET_TOKEN: 3bd139f9186b31a85336bb89cd1a1337078921134b2f48e022fd09c234d764d3e19b018b2ab789c6e0e04a1ac9e3365116368049660234c2038dc9990513d49c |
@@ -1,72 +1,86 @@ |
||
1 |
-postgresdata: |
|
2 |
- image: postgres:9.5 |
|
3 |
- command: /bin/true |
|
1 |
+# This needs at least compose 1.6.0 |
|
2 |
+version: '2' |
|
4 | 3 |
|
5 |
-postgres: |
|
6 |
- image: postgres:9.5 |
|
7 |
- volumes_from: |
|
8 |
- - postgresdata |
|
9 |
- environment: |
|
10 |
- POSTGRES_PASSWORD: myhuginnpassword |
|
11 |
- POSTGRES_USER: huginn |
|
4 |
+services: |
|
5 |
+ postgresdata: |
|
6 |
+ image: postgres:9.5 |
|
7 |
+ command: /bin/true |
|
12 | 8 |
|
13 |
-huginn_web: |
|
14 |
- image: cantino/huginn-single-process |
|
15 |
- restart: always |
|
16 |
- extends: |
|
17 |
- file: environment.yml |
|
18 |
- service: huginn_base |
|
19 |
- environment: |
|
20 |
- DATABASE_ADAPTER: postgresql |
|
21 |
- ports: |
|
22 |
- - 3000:3000 |
|
23 |
- links: |
|
24 |
- - postgres |
|
9 |
+ postgres: |
|
10 |
+ image: postgres:9.5 |
|
11 |
+ volumes_from: |
|
12 |
+ - postgresdata |
|
13 |
+ environment: |
|
14 |
+ POSTGRES_PASSWORD: myhuginnpassword |
|
15 |
+ POSTGRES_USER: huginn |
|
25 | 16 |
|
26 |
-huginn_threaded: |
|
27 |
- image: cantino/huginn-single-process |
|
28 |
- restart: always |
|
29 |
- extends: |
|
30 |
- file: environment.yml |
|
31 |
- service: huginn_base |
|
32 |
- environment: |
|
33 |
- DATABASE_ADAPTER: postgresql |
|
34 |
- links: |
|
35 |
- - postgres |
|
36 |
- command: /scripts/init bin/threaded.rb |
|
17 |
+ huginn_web: |
|
18 |
+ image: cantino/huginn-single-process |
|
19 |
+ restart: always |
|
20 |
+ extends: |
|
21 |
+ file: environment.yml |
|
22 |
+ service: huginn_base |
|
23 |
+ environment: |
|
24 |
+ DATABASE_ADAPTER: postgresql |
|
25 |
+ POSTGRES_PORT_5432_TCP_ADDR: postgres |
|
26 |
+ POSTGRES_PORT_5432_TCP_PORT: 5432 |
|
27 |
+ ports: |
|
28 |
+ - 3000:3000 |
|
29 |
+ links: |
|
30 |
+ - postgres |
|
37 | 31 |
|
38 |
-# huginn_schedule: |
|
39 |
-# image: cantino/huginn-single-process |
|
40 |
-# extends: |
|
41 |
-# file: environment.yml |
|
42 |
-# service: huginn_base |
|
43 |
-# environment: |
|
44 |
-# DATABASE_ADAPTER: postgresql |
|
45 |
-# links: |
|
46 |
-# - postgres |
|
47 |
-# command: /scripts/init bin/schedule.rb |
|
32 |
+ huginn_threaded: |
|
33 |
+ image: cantino/huginn-single-process |
|
34 |
+ restart: always |
|
35 |
+ extends: |
|
36 |
+ file: environment.yml |
|
37 |
+ service: huginn_base |
|
38 |
+ environment: |
|
39 |
+ DATABASE_ADAPTER: postgresql |
|
40 |
+ POSTGRES_PORT_5432_TCP_ADDR: postgres |
|
41 |
+ POSTGRES_PORT_5432_TCP_PORT: 5432 |
|
42 |
+ links: |
|
43 |
+ - postgres |
|
44 |
+ command: /scripts/init bin/threaded.rb |
|
48 | 45 |
|
46 |
+ # huginn_schedule: |
|
47 |
+ # image: cantino/huginn-single-process |
|
48 |
+ # extends: |
|
49 |
+ # file: environment.yml |
|
50 |
+ # service: huginn_base |
|
51 |
+ # environment: |
|
52 |
+ # DATABASE_ADAPTER: postgresql |
|
53 |
+ # POSTGRES_PORT_5432_TCP_ADDR: postgres |
|
54 |
+ # POSTGRES_PORT_5432_TCP_PORT: 5432 |
|
55 |
+ # links: |
|
56 |
+ # - postgres |
|
57 |
+ # command: /scripts/init bin/schedule.rb |
|
49 | 58 |
|
50 |
-# huginn_twitter_stream: |
|
51 |
-# image: cantino/huginn-single-process |
|
52 |
-# extends: |
|
53 |
-# file: environment.yml |
|
54 |
-# service: huginn_base |
|
55 |
-# environment: |
|
56 |
-# DATABASE_ADAPTER: postgresql |
|
57 |
-# links: |
|
58 |
-# - postgres |
|
59 |
-# command: /scripts/init bin/twitter_stream.rb |
|
60 | 59 |
|
60 |
+ # huginn_twitter_stream: |
|
61 |
+ # image: cantino/huginn-single-process |
|
62 |
+ # extends: |
|
63 |
+ # file: environment.yml |
|
64 |
+ # service: huginn_base |
|
65 |
+ # environment: |
|
66 |
+ # DATABASE_ADAPTER: postgresql |
|
67 |
+ # POSTGRES_PORT_5432_TCP_ADDR: postgres |
|
68 |
+ # POSTGRES_PORT_5432_TCP_PORT: 5432 |
|
69 |
+ # links: |
|
70 |
+ # - postgres |
|
71 |
+ # command: /scripts/init bin/twitter_stream.rb |
|
61 | 72 |
|
62 |
-# huginn_dj1: |
|
63 |
-# image: cantino/huginn-single-process |
|
64 |
-# extends: |
|
65 |
-# file: environment.yml |
|
66 |
-# service: huginn_base |
|
67 |
-# environment: |
|
68 |
-# DATABASE_ADAPTER: postgresql |
|
69 |
-# links: |
|
70 |
-# - postgres |
|
71 |
-# command: /scripts/init script/delayed_job run |
|
73 |
+ |
|
74 |
+ # huginn_dj1: |
|
75 |
+ # image: cantino/huginn-single-process |
|
76 |
+ # extends: |
|
77 |
+ # file: environment.yml |
|
78 |
+ # service: huginn_base |
|
79 |
+ # environment: |
|
80 |
+ # DATABASE_ADAPTER: postgresql |
|
81 |
+ # POSTGRES_PORT_5432_TCP_ADDR: postgres |
|
82 |
+ # POSTGRES_PORT_5432_TCP_PORT: 5432 |
|
83 |
+ # links: |
|
84 |
+ # - postgres |
|
85 |
+ # command: /scripts/init script/delayed_job run |
|
72 | 86 |
|
@@ -29,7 +29,7 @@ grep = /app/.env.example | sed -e 's/^#\([^ ]\)/\1/' | grep -v -e '^#' | \ |
||
29 | 29 |
|
30 | 30 |
eval "echo PORT=${PORT:-${PORT:-3000}}" >> .env |
31 | 31 |
eval "echo RAILS_ENV=${RAILS_ENV:-${RAILS_ENV:-production}}" >> .env |
32 |
-eval "echo ON_HEROKU=true" >> .env |
|
32 |
+eval "echo RAILS_LOG_TO_STDOUT=true" >> .env |
|
33 | 33 |
eval "echo RAILS_SERVE_STATIC_FILES=true" >> .env |
34 | 34 |
|
35 | 35 |
chmod ugo+r /app/.env |
@@ -1,13 +0,0 @@ |
||
1 |
-require 'active_support' |
|
2 |
- |
|
3 |
-ActiveSupport.on_load :active_record do |
|
4 |
- class << ActiveRecord::Base |
|
5 |
- def establish_connection(spec = nil) |
|
6 |
- super.tap { |ret| |
|
7 |
- if /mysql/i === connection.adapter_name |
|
8 |
- require 'ar_mysql_column_charset/main' |
|
9 |
- end |
|
10 |
- } |
|
11 |
- end |
|
12 |
- end |
|
13 |
-end |
@@ -1,118 +0,0 @@ |
||
1 |
-raise "Do not directly load this library." unless defined?(ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter) |
|
2 |
- |
|
3 |
-module ActiveRecord::ConnectionAdapters |
|
4 |
- class ColumnDefinition |
|
5 |
- module CharsetSupport |
|
6 |
- attr_accessor :charset, :collation |
|
7 |
- end |
|
8 |
- |
|
9 |
- prepend CharsetSupport |
|
10 |
- end |
|
11 |
- |
|
12 |
- class TableDefinition |
|
13 |
- module CharsetSupport |
|
14 |
- def new_column_definition(name, type, options) |
|
15 |
- column = super |
|
16 |
- column.charset = options[:charset] |
|
17 |
- column.collation = options[:collation] |
|
18 |
- column |
|
19 |
- end |
|
20 |
- end |
|
21 |
- |
|
22 |
- prepend CharsetSupport |
|
23 |
- end |
|
24 |
- |
|
25 |
- class AbstractMysqlAdapter |
|
26 |
- module CharsetSupport |
|
27 |
- def prepare_column_options(column, types) |
|
28 |
- spec = super |
|
29 |
- spec[:charset] = column.charset.inspect if column.charset && column.charset != charset |
|
30 |
- spec[:collation] = column.collation.inspect if column.collation && column.collation != collation |
|
31 |
- spec |
|
32 |
- end |
|
33 |
- |
|
34 |
- def migration_keys |
|
35 |
- super + [:charset, :collation] |
|
36 |
- end |
|
37 |
- |
|
38 |
- def utf8mb4_supported? |
|
39 |
- if @utf8mb4_supported.nil? |
|
40 |
- @utf8mb4_supported = !select("show character set like 'utf8mb4'").empty? |
|
41 |
- else |
|
42 |
- @utf8mb4_supported |
|
43 |
- end |
|
44 |
- end |
|
45 |
- |
|
46 |
- def charset_collation(charset, collation) |
|
47 |
- [charset, collation].map { |name| |
|
48 |
- case name |
|
49 |
- when nil |
|
50 |
- nil |
|
51 |
- when /\A(utf8mb4(_\w*)?)\z/ |
|
52 |
- if utf8mb4_supported? |
|
53 |
- $1 |
|
54 |
- else |
|
55 |
- "utf8#{$2}" |
|
56 |
- end |
|
57 |
- else |
|
58 |
- name.to_s |
|
59 |
- end |
|
60 |
- } |
|
61 |
- end |
|
62 |
- |
|
63 |
- def create_database(name, options = {}) |
|
64 |
- # utf8mb4 is used in column definitions; use utf8 for |
|
65 |
- # databases. |
|
66 |
- [:charset, :collation].each { |key| |
|
67 |
- case options[key] |
|
68 |
- when /\A(utf8mb4(_\w*)?)\z/ |
|
69 |
- options = options.merge(key => "utf8#{$2}") |
|
70 |
- end |
|
71 |
- } |
|
72 |
- super(name, options) |
|
73 |
- end |
|
74 |
- end |
|
75 |
- |
|
76 |
- prepend CharsetSupport |
|
77 |
- |
|
78 |
- class SchemaCreation |
|
79 |
- module CharsetSupport |
|
80 |
- def column_options(o) |
|
81 |
- column_options = super |
|
82 |
- column_options[:charset] = o.charset unless o.charset.nil? |
|
83 |
- column_options[:collation] = o.collation unless o.collation.nil? |
|
84 |
- column_options |
|
85 |
- end |
|
86 |
- |
|
87 |
- def add_column_options!(sql, options) |
|
88 |
- charset, collation = @conn.charset_collation(options[:charset], options[:collation]) |
|
89 |
- |
|
90 |
- if charset |
|
91 |
- sql << " CHARACTER SET #{charset}" |
|
92 |
- end |
|
93 |
- |
|
94 |
- if collation |
|
95 |
- sql << " COLLATE #{collation}" |
|
96 |
- end |
|
97 |
- |
|
98 |
- super |
|
99 |
- end |
|
100 |
- end |
|
101 |
- |
|
102 |
- prepend CharsetSupport |
|
103 |
- end |
|
104 |
- |
|
105 |
- class Column |
|
106 |
- module CharsetSupport |
|
107 |
- attr_reader :charset |
|
108 |
- |
|
109 |
- def initialize(*args) |
|
110 |
- super |
|
111 |
- @charset = @collation[/\A[^_]+/] unless @collation.nil? |
|
112 |
- end |
|
113 |
- end |
|
114 |
- |
|
115 |
- prepend CharsetSupport |
|
116 |
- end |
|
117 |
- end |
|
118 |
-end |
@@ -0,0 +1,286 @@ |
||
1 |
+require 'feedjira' |
|
2 |
+require 'digest' |
|
3 |
+require 'mail' |
|
4 |
+ |
|
5 |
+module FeedjiraExtension |
|
6 |
+ AUTHOR_ATTRS = %i[name email uri] |
|
7 |
+ LINK_ATTRS = %i[href rel type hreflang title length] |
|
8 |
+ ENCLOSURE_ATTRS = %i[url type length] |
|
9 |
+ |
|
10 |
+ class Author < Struct.new(*AUTHOR_ATTRS) |
|
11 |
+ def to_json(options = nil) |
|
12 |
+ members.flat_map { |key| |
|
13 |
+ if value = self[key].presence |
|
14 |
+ case key |
|
15 |
+ when :email |
|
16 |
+ "<#{value}>" |
|
17 |
+ when :uri |
|
18 |
+ "(#{value})" |
|
19 |
+ else |
|
20 |
+ value |
|
21 |
+ end |
|
22 |
+ else |
|
23 |
+ [] |
|
24 |
+ end |
|
25 |
+ }.join(' ').to_json(options) |
|
26 |
+ end |
|
27 |
+ end |
|
28 |
+ |
|
29 |
+ class AtomAuthor < Author |
|
30 |
+ include SAXMachine |
|
31 |
+ |
|
32 |
+ AUTHOR_ATTRS.each do |attr| |
|
33 |
+ element attr |
|
34 |
+ end |
|
35 |
+ end |
|
36 |
+ |
|
37 |
+ class RssAuthor < Author |
|
38 |
+ include SAXMachine |
|
39 |
+ |
|
40 |
+ def content=(content) |
|
41 |
+ @content = content |
|
42 |
+ |
|
43 |
+ begin |
|
44 |
+ addr = Mail::Address.new(content) |
|
45 |
+ rescue |
|
46 |
+ self.name = content |
|
47 |
+ else |
|
48 |
+ self.name = addr.name |
|
49 |
+ self.email = addr.address |
|
50 |
+ end |
|
51 |
+ end |
|
52 |
+ |
|
53 |
+ value :content |
|
54 |
+ end |
|
55 |
+ |
|
56 |
+ class Enclosure |
|
57 |
+ include SAXMachine |
|
58 |
+ |
|
59 |
+ ENCLOSURE_ATTRS.each do |attr| |
|
60 |
+ attribute attr |
|
61 |
+ end |
|
62 |
+ |
|
63 |
+ def to_json(options = nil) |
|
64 |
+ ENCLOSURE_ATTRS.each_with_object({}) { |key, hash| |
|
65 |
+ if value = __send__(key) |
|
66 |
+ hash[key] = value |
|
67 |
+ end |
|
68 |
+ }.to_json(options) |
|
69 |
+ end |
|
70 |
+ end |
|
71 |
+ |
|
72 |
+ class AtomLink |
|
73 |
+ include SAXMachine |
|
74 |
+ |
|
75 |
+ LINK_ATTRS.each do |attr| |
|
76 |
+ attribute attr |
|
77 |
+ end |
|
78 |
+ |
|
79 |
+ def to_json(options = nil) |
|
80 |
+ LINK_ATTRS.each_with_object({}) { |key, hash| |
|
81 |
+ if value = __send__(key) |
|
82 |
+ hash[key] = value |
|
83 |
+ end |
|
84 |
+ }.to_json(options) |
|
85 |
+ end |
|
86 |
+ end |
|
87 |
+ |
|
88 |
+ class RssLinkElement |
|
89 |
+ include SAXMachine |
|
90 |
+ |
|
91 |
+ value :href |
|
92 |
+ |
|
93 |
+ def to_json(options = nil) |
|
94 |
+ { |
|
95 |
+ href: href |
|
96 |
+ }.to_json(options) |
|
97 |
+ end |
|
98 |
+ end |
|
99 |
+ |
|
100 |
+ module HasAuthors |
|
101 |
+ def self.included(mod) |
|
102 |
+ mod.module_exec do |
|
103 |
+ case name |
|
104 |
+ when /RSS/ |
|
105 |
+ %w[ |
|
106 |
+ itunes:author |
|
107 |
+ dc:creator |
|
108 |
+ author |
|
109 |
+ managingEditor |
|
110 |
+ ].each do |name| |
|
111 |
+ sax_config.top_level_elements[name].clear |
|
112 |
+ |
|
113 |
+ elements name, class: RssAuthor, as: :authors |
|
114 |
+ end |
|
115 |
+ else |
|
116 |
+ elements :author, class: AtomAuthor, as: :authors |
|
117 |
+ end |
|
118 |
+ |
|
119 |
+ def alternate_link |
|
120 |
+ links.find { |link| |
|
121 |
+ link.is_a?(AtomLink) && |
|
122 |
+ link.rel == 'alternate' && |
|
123 |
+ (link.type == 'text/html'|| link.type.nil?) |
|
124 |
+ } |
|
125 |
+ end |
|
126 |
+ |
|
127 |
+ def url |
|
128 |
+ @url ||= (alternate_link || links.first).try!(:href) |
|
129 |
+ end |
|
130 |
+ end |
|
131 |
+ end |
|
132 |
+ end |
|
133 |
+ |
|
134 |
+ module HasEnclosure |
|
135 |
+ def self.included(mod) |
|
136 |
+ mod.module_exec do |
|
137 |
+ sax_config.top_level_elements['enclosure'].clear |
|
138 |
+ |
|
139 |
+ element :enclosure, class: Enclosure |
|
140 |
+ |
|
141 |
+ def image_enclosure |
|
142 |
+ case enclosure.try!(:type) |
|
143 |
+ when %r{\Aimage/} |
|
144 |
+ enclosure |
|
145 |
+ end |
|
146 |
+ end |
|
147 |
+ |
|
148 |
+ def image |
|
149 |
+ @image ||= image_enclosure.try!(:url) |
|
150 |
+ end |
|
151 |
+ end |
|
152 |
+ end |
|
153 |
+ end |
|
154 |
+ |
|
155 |
+ module HasLinks |
|
156 |
+ def self.included(mod) |
|
157 |
+ mod.module_exec do |
|
158 |
+ sax_config.top_level_elements['link'].clear |
|
159 |
+ sax_config.collection_elements['link'].clear |
|
160 |
+ |
|
161 |
+ case name |
|
162 |
+ when /RSS/ |
|
163 |
+ elements :link, class: RssLinkElement, as: :rss_links |
|
164 |
+ |
|
165 |
+ case name |
|
166 |
+ when /FeedBurner/ |
|
167 |
+ elements :'atok10:link', class: AtomLink, as: :atom_links |
|
168 |
+ |
|
169 |
+ def links |
|
170 |
+ @links ||= [*rss_links, *atom_links] |
|
171 |
+ end |
|
172 |
+ else |
|
173 |
+ alias_method :links, :rss_links |
|
174 |
+ end |
|
175 |
+ else |
|
176 |
+ elements :link, class: AtomLink, as: :links |
|
177 |
+ end |
|
178 |
+ |
|
179 |
+ def alternate_link |
|
180 |
+ links.find { |link| |
|
181 |
+ link.is_a?(AtomLink) && |
|
182 |
+ link.rel == 'alternate' && |
|
183 |
+ (link.type == 'text/html'|| link.type.nil?) |
|
184 |
+ } |
|
185 |
+ end |
|
186 |
+ |
|
187 |
+ def url |
|
188 |
+ @url ||= (alternate_link || links.first).try!(:href) |
|
189 |
+ end |
|
190 |
+ end |
|
191 |
+ end |
|
192 |
+ end |
|
193 |
+ |
|
194 |
+ module HasTimestamps |
|
195 |
+ attr_reader :published, :updated |
|
196 |
+ |
|
197 |
+ # Keep the "oldest" publish time found |
|
198 |
+ def published=(value) |
|
199 |
+ parsed = parse_datetime(value) |
|
200 |
+ @published = parsed if !@published || parsed < @published |
|
201 |
+ end |
|
202 |
+ |
|
203 |
+ # Keep the most recent update time found |
|
204 |
+ def updated=(value) |
|
205 |
+ parsed = parse_datetime(value) |
|
206 |
+ @updated = parsed if !@updated || parsed > @updated |
|
207 |
+ end |
|
208 |
+ |
|
209 |
+ def date_published |
|
210 |
+ published.try(:iso8601) |
|
211 |
+ end |
|
212 |
+ |
|
213 |
+ def last_updated |
|
214 |
+ (updated || published).try(:iso8601) |
|
215 |
+ end |
|
216 |
+ |
|
217 |
+ private |
|
218 |
+ |
|
219 |
+ def parse_datetime(string) |
|
220 |
+ DateTime.parse(string) rescue nil |
|
221 |
+ end |
|
222 |
+ end |
|
223 |
+ |
|
224 |
+ module FeedEntryExtensions |
|
225 |
+ def self.included(mod) |
|
226 |
+ mod.module_exec do |
|
227 |
+ include HasAuthors |
|
228 |
+ include HasEnclosure |
|
229 |
+ include HasLinks |
|
230 |
+ include HasTimestamps |
|
231 |
+ end |
|
232 |
+ end |
|
233 |
+ |
|
234 |
+ def id |
|
235 |
+ entry_id || Digest::MD5.hexdigest(content || summary || '') |
|
236 |
+ end |
|
237 |
+ end |
|
238 |
+ |
|
239 |
+ module FeedExtensions |
|
240 |
+ def self.included(mod) |
|
241 |
+ mod.module_exec do |
|
242 |
+ include HasAuthors |
|
243 |
+ include HasEnclosure |
|
244 |
+ include HasLinks |
|
245 |
+ include HasTimestamps |
|
246 |
+ |
|
247 |
+ element :id, as: :feed_id |
|
248 |
+ element :generator |
|
249 |
+ elements :rights |
|
250 |
+ element :published |
|
251 |
+ element :updated |
|
252 |
+ element :icon |
|
253 |
+ |
|
254 |
+ if /RSS/ === name |
|
255 |
+ element :guid, as: :feed_id |
|
256 |
+ element :copyright |
|
257 |
+ element :pubDate, as: :published |
|
258 |
+ element :'dc:date', as: :published |
|
259 |
+ element :lastBuildDate, as: :updated |
|
260 |
+ element :image, value: :url, as: :icon |
|
261 |
+ |
|
262 |
+ def copyright |
|
263 |
+ @copyright || super |
|
264 |
+ end |
|
265 |
+ end |
|
266 |
+ |
|
267 |
+ sax_config.collection_elements.each_value do |collection_elements| |
|
268 |
+ collection_elements.each do |collection_element| |
|
269 |
+ collection_element.accessor == 'entries' && |
|
270 |
+ (entry_class = collection_element.data_class).is_a?(Class) or next |
|
271 |
+ |
|
272 |
+ entry_class.send :include, FeedEntryExtensions |
|
273 |
+ end |
|
274 |
+ end |
|
275 |
+ end |
|
276 |
+ end |
|
277 |
+ |
|
278 |
+ def copyright |
|
279 |
+ rights.join("\n").presence |
|
280 |
+ end |
|
281 |
+ end |
|
282 |
+ |
|
283 |
+ Feedjira::Feed.feed_classes.each do |feed_class| |
|
284 |
+ feed_class.send :include, FeedExtensions |
|
285 |
+ end |
|
286 |
+end |
@@ -1 +0,0 @@ |
||
1 |
-require 'ar_mysql_column_charset' |
@@ -34,7 +34,7 @@ namespace :production do |
||
34 | 34 |
end |
35 | 35 |
|
36 | 36 |
task :start => :check do |
37 |
- puts "Startig huginn ..." |
|
37 |
+ puts "Starting huginn ..." |
|
38 | 38 |
run_sv('start') |
39 | 39 |
end |
40 | 40 |
|
@@ -1,5 +1,6 @@ |
||
1 | 1 |
require 'jsonpath' |
2 | 2 |
require 'cgi' |
3 |
+require 'addressable/uri' |
|
3 | 4 |
|
4 | 5 |
module Utils |
5 | 6 |
def self.unindent(s) |
@@ -25,11 +26,29 @@ module Utils |
||
25 | 26 |
begin |
26 | 27 |
URI(uri) |
27 | 28 |
rescue URI::Error |
28 |
- URI(uri.to_s.gsub(/[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]+/) { |unsafe| |
|
29 |
- unsafe.bytes.each_with_object(String.new) { |uc, s| |
|
30 |
- s << sprintf('%%%02X', uc) |
|
31 |
- } |
|
32 |
- }.force_encoding(Encoding::US_ASCII)) |
|
29 |
+ begin |
|
30 |
+ URI(uri.to_s.gsub(/[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]+/) { |unsafe| |
|
31 |
+ unsafe.bytes.each_with_object(String.new) { |uc, s| |
|
32 |
+ s << sprintf('%%%02X', uc) |
|
33 |
+ } |
|
34 |
+ }.force_encoding(Encoding::US_ASCII)) |
|
35 |
+ rescue URI::Error => e |
|
36 |
+ begin |
|
37 |
+ auri = Addressable::URI.parse(uri.to_s) |
|
38 |
+ rescue |
|
39 |
+ # Do not leak Addressable::URI::InvalidURIError which |
|
40 |
+ # callers might not expect. |
|
41 |
+ raise e |
|
42 |
+ else |
|
43 |
+ # Addressable::URI#normalize! modifies the query and |
|
44 |
+ # fragment components beyond escaping unsafe characters, so |
|
45 |
+ # avoid using it. Otherwise `?a[]=%2F` would be normalized |
|
46 |
+ # as `?a%5B%5D=/`, for example. |
|
47 |
+ auri.site = auri.normalized_site |
|
48 |
+ auri.path = auri.normalized_path |
|
49 |
+ URI(auri.to_s) |
|
50 |
+ end |
|
51 |
+ end |
|
33 | 52 |
end |
34 | 53 |
end |
35 | 54 |
|
@@ -106,6 +106,10 @@ describe LiquidInterpolatable::Filters do |
||
106 | 106 |
expect(@filter.to_uri(123, 'http://example.com/dir/1')).to eq(URI('http://example.com/dir/123')) |
107 | 107 |
end |
108 | 108 |
|
109 |
+ it 'should normalize a URL' do |
|
110 |
+ expect(@filter.to_uri('a[]', 'http://example.com/dir/1')).to eq(URI('http://example.com/dir/a%5B%5D')) |
|
111 |
+ end |
|
112 |
+ |
|
109 | 113 |
it 'should return a URI value in interpolation' do |
110 | 114 |
expect(@agent.interpolated['foo']).to eq('/dir/1') |
111 | 115 |
end |
@@ -140,8 +144,8 @@ describe LiquidInterpolatable::Filters do |
||
140 | 144 |
expect(@filter.uri_expand(nil)).to eq('') |
141 | 145 |
expect(@filter.uri_expand('')).to eq('') |
142 | 146 |
expect(@filter.uri_expand(5)).to eq('5') |
143 |
- expect(@filter.uri_expand([])).to eq('[]') |
|
144 |
- expect(@filter.uri_expand({})).to eq('{}') |
|
147 |
+ expect(@filter.uri_expand([])).to eq('%5B%5D') |
|
148 |
+ expect(@filter.uri_expand({})).to eq('%7B%7D') |
|
145 | 149 |
expect(@filter.uri_expand(URI('/'))).to eq('/') |
146 | 150 |
expect(@filter.uri_expand(URI('http:google.com'))).to eq('http:google.com') |
147 | 151 |
expect(@filter.uri_expand(URI('http:/google.com'))).to eq('http:/google.com') |
@@ -264,4 +268,59 @@ describe LiquidInterpolatable::Filters do |
||
264 | 268 |
expect(agent.interpolated['cleaned']).to eq('FOObar ZOObar') |
265 | 269 |
end |
266 | 270 |
end |
271 |
+ |
|
272 |
+ context 'as_object' do |
|
273 |
+ let(:agent) { Agents::InterpolatableAgent.new(name: "test") } |
|
274 |
+ |
|
275 |
+ it 'returns an array that was splitted in liquid tags' do |
|
276 |
+ agent.interpolation_context['something'] = 'test,string,abc' |
|
277 |
+ agent.options['array'] = "{{something | split: ',' | as_object}}" |
|
278 |
+ expect(agent.interpolated['array']).to eq(['test', 'string', 'abc']) |
|
279 |
+ end |
|
280 |
+ |
|
281 |
+ it 'returns an object that was not modified in liquid' do |
|
282 |
+ agent.interpolation_context['something'] = {'nested' => {'abc' => 'test'}} |
|
283 |
+ agent.options['object'] = "{{something.nested | as_object}}" |
|
284 |
+ expect(agent.interpolated['object']).to eq({"abc" => 'test'}) |
|
285 |
+ end |
|
286 |
+ |
|
287 |
+ context 'as_json' do |
|
288 |
+ def ensure_safety(obj) |
|
289 |
+ JSON.parse(JSON.dump(obj)) |
|
290 |
+ end |
|
291 |
+ |
|
292 |
+ it 'it converts "complex" objects' do |
|
293 |
+ agent.interpolation_context['something'] = {'nested' => Service.new} |
|
294 |
+ agent.options['object'] = "{{something | as_object}}" |
|
295 |
+ expect(agent.interpolated['object']).to eq({'nested'=> ensure_safety(Service.new.as_json)}) |
|
296 |
+ end |
|
297 |
+ |
|
298 |
+ it 'works with AgentDrops' do |
|
299 |
+ agent.interpolation_context['something'] = agent |
|
300 |
+ agent.options['object'] = "{{something | as_object}}" |
|
301 |
+ expect(agent.interpolated['object']).to eq(ensure_safety(agent.to_liquid.as_json.stringify_keys)) |
|
302 |
+ end |
|
303 |
+ |
|
304 |
+ it 'works with EventDrops' do |
|
305 |
+ event = Event.new(payload: {some: 'payload'}, agent: agent, created_at: Time.now) |
|
306 |
+ agent.interpolation_context['something'] = event |
|
307 |
+ agent.options['object'] = "{{something | as_object}}" |
|
308 |
+ expect(agent.interpolated['object']).to eq(ensure_safety(event.to_liquid.as_json.stringify_keys)) |
|
309 |
+ end |
|
310 |
+ |
|
311 |
+ it 'works with MatchDataDrops' do |
|
312 |
+ match = "test string".match(/\A(?<word>\w+)\s(.+?)\z/) |
|
313 |
+ agent.interpolation_context['something'] = match |
|
314 |
+ agent.options['object'] = "{{something | as_object}}" |
|
315 |
+ expect(agent.interpolated['object']).to eq(ensure_safety(match.to_liquid.as_json.stringify_keys)) |
|
316 |
+ end |
|
317 |
+ |
|
318 |
+ it 'works with URIDrops' do |
|
319 |
+ uri = URI.parse("https://google.com?q=test") |
|
320 |
+ agent.interpolation_context['something'] = uri |
|
321 |
+ agent.options['object'] = "{{something | as_object}}" |
|
322 |
+ expect(agent.interpolated['object']).to eq(ensure_safety(uri.to_liquid.as_json.stringify_keys)) |
|
323 |
+ end |
|
324 |
+ end |
|
325 |
+ end |
|
267 | 326 |
end |
@@ -6,8 +6,8 @@ describe Admin::UsersController do |
||
6 | 6 |
it 'imports the default scenario for the new user' do |
7 | 7 |
mock(DefaultScenarioImporter).import(is_a(User)) |
8 | 8 |
sign_in users(:jane) |
9 |
- post :create, :user => {username: 'jdoe', email: 'jdoe@example.com', |
|
10 |
- password: 's3cr3t55', password_confirmation: 's3cr3t55', admin: false } |
|
9 |
+ post :create, params: {:user => {username: 'jdoe', email: 'jdoe@example.com', |
|
10 |
+ password: 's3cr3t55', password_confirmation: 's3cr3t55', admin: false }} |
|
11 | 11 |
end |
12 | 12 |
end |
13 | 13 |
|
@@ -15,8 +15,45 @@ describe Admin::UsersController do |
||
15 | 15 |
it 'does not import the default scenario' do |
16 | 16 |
stub(DefaultScenarioImporter).import(is_a(User)) { fail "Should not attempt import" } |
17 | 17 |
sign_in users(:jane) |
18 |
- post :create, :user => {} |
|
18 |
+ post :create, params: {:user => {username: 'user'}} |
|
19 | 19 |
end |
20 | 20 |
end |
21 | 21 |
end |
22 |
+ |
|
23 |
+ describe 'GET #switch_to_user' do |
|
24 |
+ it "switches to another user" do |
|
25 |
+ sign_in users(:jane) |
|
26 |
+ |
|
27 |
+ get :switch_to_user, params: {:id => users(:bob).id} |
|
28 |
+ expect(response).to redirect_to(agents_path) |
|
29 |
+ expect(subject.session[:original_admin_user_id]).to eq(users(:jane).id) |
|
30 |
+ end |
|
31 |
+ |
|
32 |
+ it "does not switch if not admin" do |
|
33 |
+ sign_in users(:bob) |
|
34 |
+ |
|
35 |
+ get :switch_to_user, params: {:id => users(:jane).id} |
|
36 |
+ expect(response).to redirect_to(root_path) |
|
37 |
+ end |
|
38 |
+ end |
|
39 |
+ |
|
40 |
+ describe 'GET #switch_back' do |
|
41 |
+ it "switches to another user and back" do |
|
42 |
+ sign_in users(:jane) |
|
43 |
+ |
|
44 |
+ get :switch_to_user, params: {:id => users(:bob).id} |
|
45 |
+ expect(response).to redirect_to(agents_path) |
|
46 |
+ expect(subject.session[:original_admin_user_id]).to eq(users(:jane).id) |
|
47 |
+ |
|
48 |
+ get :switch_back |
|
49 |
+ expect(response).to redirect_to(admin_users_path) |
|
50 |
+ expect(subject.session[:original_admin_user_id]).to be_nil |
|
51 |
+ end |
|
52 |
+ |
|
53 |
+ it "does not switch_back without having switched" do |
|
54 |
+ sign_in users(:bob) |
|
55 |
+ get :switch_back |
|
56 |
+ expect(response).to redirect_to(root_path) |
|
57 |
+ end |
|
58 |
+ end |
|
22 | 59 |
end |
@@ -16,7 +16,7 @@ describe Agents::DryRunsController do |
||
16 | 16 |
|
17 | 17 |
describe "GET index" do |
18 | 18 |
it "does not load any events without specifing sources" do |
19 |
- get :index, type: 'Agents::WebsiteAgent', source_ids: [] |
|
19 |
+ get :index, params: {type: 'Agents::WebsiteAgent', source_ids: []} |
|
20 | 20 |
expect(assigns(:events)).to eq([]) |
21 | 21 |
end |
22 | 22 |
|
@@ -29,13 +29,13 @@ describe Agents::DryRunsController do |
||
29 | 29 |
end |
30 | 30 |
|
31 | 31 |
it "for new agents" do |
32 |
- get :index, type: 'Agents::WebsiteAgent', source_ids: [@agent.id] |
|
32 |
+ get :index, params: {type: 'Agents::WebsiteAgent', source_ids: [@agent.id]} |
|
33 | 33 |
expect(assigns(:events)).to eq([]) |
34 | 34 |
end |
35 | 35 |
|
36 | 36 |
it "for existing agents" do |
37 | 37 |
expect(@agent.events.count).not_to be(0) |
38 |
- expect { get :index, agent_id: @agent }.to raise_error(NoMethodError) |
|
38 |
+ expect { get :index, params: {agent_id: @agent} }.to raise_error(NoMethodError) |
|
39 | 39 |
end |
40 | 40 |
end |
41 | 41 |
|
@@ -47,12 +47,12 @@ describe Agents::DryRunsController do |
||
47 | 47 |
end |
48 | 48 |
|
49 | 49 |
it "load the most recent events when providing source ids" do |
50 |
- get :index, type: 'Agents::WebsiteAgent', source_ids: [@agent.id] |
|
50 |
+ get :index, params: {type: 'Agents::WebsiteAgent', source_ids: [@agent.id]} |
|
51 | 51 |
expect(assigns(:events)).to eq([@agent.events.first]) |
52 | 52 |
end |
53 | 53 |
|
54 | 54 |
it "loads the most recent events for a saved agent" do |
55 |
- get :index, agent_id: @agent |
|
55 |
+ get :index, params: {agent_id: @agent} |
|
56 | 56 |
expect(assigns(:events)).to eq([@agent.events.first]) |
57 | 57 |
end |
58 | 58 |
end |
@@ -65,7 +65,7 @@ describe Agents::DryRunsController do |
||
65 | 65 |
|
66 | 66 |
it "does not actually create any agent, event or log" do |
67 | 67 |
expect { |
68 |
- post :create, agent: valid_attributes |
|
68 |
+ post :create, params: {agent: valid_attributes} |
|
69 | 69 |
}.not_to change { |
70 | 70 |
[users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count] |
71 | 71 |
} |
@@ -81,7 +81,7 @@ describe Agents::DryRunsController do |
||
81 | 81 |
it "does not actually update an agent" do |
82 | 82 |
agent = agents(:bob_weather_agent) |
83 | 83 |
expect { |
84 |
- post :create, agent_id: agent, agent: valid_attributes(name: 'New Name') |
|
84 |
+ post :create, params: {agent_id: agent, agent: valid_attributes(name: 'New Name')} |
|
85 | 85 |
}.not_to change { |
86 | 86 |
[users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at] |
87 | 87 |
} |
@@ -93,7 +93,7 @@ describe Agents::DryRunsController do |
||
93 | 93 |
agent.save! |
94 | 94 |
url_from_event = "http://xkcd.com/?from_event=1".freeze |
95 | 95 |
expect { |
96 |
- post :create, agent_id: agent, event: { url: url_from_event } |
|
96 |
+ post :create, params: {agent_id: agent, event: { url: url_from_event }.to_json} |
|
97 | 97 |
}.not_to change { |
98 | 98 |
[users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at] |
99 | 99 |
} |
@@ -112,7 +112,7 @@ describe Agents::DryRunsController do |
||
112 | 112 |
agent.memory = {fu: "bar"} |
113 | 113 |
agent.user = users(:bob) |
114 | 114 |
agent.save! |
115 |
- post :create, agent_id: agent, agent: valid_params |
|
115 |
+ post :create, params: {agent_id: agent, agent: valid_params} |
|
116 | 116 |
results = assigns(:results) |
117 | 117 |
expect(results[:events][0]).to eql({"message" => "bar"}) |
118 | 118 |
end |
@@ -29,7 +29,7 @@ describe AgentsController do |
||
29 | 29 |
describe "POST handle_details_post" do |
30 | 30 |
it "passes control to handle_details_post on the agent" do |
31 | 31 |
sign_in users(:bob) |
32 |
- post :handle_details_post, :id => agents(:bob_manual_event_agent).to_param, :payload => { :foo => "bar" }.to_json |
|
32 |
+ post :handle_details_post, params: {:id => agents(:bob_manual_event_agent).to_param, :payload => { :foo => "bar" }.to_json} |
|
33 | 33 |
expect(JSON.parse(response.body)).to eq({ "success" => true }) |
34 | 34 |
expect(agents(:bob_manual_event_agent).events.last.payload).to eq({ 'foo' => "bar" }) |
35 | 35 |
end |
@@ -37,7 +37,7 @@ describe AgentsController do |
||
37 | 37 |
it "can only be accessed by the Agent's owner" do |
38 | 38 |
sign_in users(:jane) |
39 | 39 |
expect { |
40 |
- post :handle_details_post, :id => agents(:bob_manual_event_agent).to_param, :payload => { :foo => :bar }.to_json |
|
40 |
+ post :handle_details_post, params: {:id => agents(:bob_manual_event_agent).to_param, :payload => { :foo => :bar }.to_json} |
|
41 | 41 |
}.to raise_error(ActiveRecord::RecordNotFound) |
42 | 42 |
end |
43 | 43 |
end |
@@ -46,13 +46,13 @@ describe AgentsController do |
||
46 | 46 |
it "triggers Agent.async_check with the Agent's ID" do |
47 | 47 |
sign_in users(:bob) |
48 | 48 |
mock(Agent).async_check(agents(:bob_manual_event_agent).id) |
49 |
- post :run, :id => agents(:bob_manual_event_agent).to_param |
|
49 |
+ post :run, params: {:id => agents(:bob_manual_event_agent).to_param} |
|
50 | 50 |
end |
51 | 51 |
|
52 | 52 |
it "can only be accessed by the Agent's owner" do |
53 | 53 |
sign_in users(:jane) |
54 | 54 |
expect { |
55 |
- post :run, :id => agents(:bob_manual_event_agent).to_param |
|
55 |
+ post :run, params: {:id => agents(:bob_manual_event_agent).to_param} |
|
56 | 56 |
}.to raise_error(ActiveRecord::RecordNotFound) |
57 | 57 |
end |
58 | 58 |
end |
@@ -62,7 +62,7 @@ describe AgentsController do |
||
62 | 62 |
sign_in users(:bob) |
63 | 63 |
agent_event = events(:bob_website_agent_event).id |
64 | 64 |
other_event = events(:jane_website_agent_event).id |
65 |
- post :remove_events, :id => agents(:bob_website_agent).to_param |
|
65 |
+ post :remove_events, params: {:id => agents(:bob_website_agent).to_param} |
|
66 | 66 |
expect(Event.where(:id => agent_event).count).to eq(0) |
67 | 67 |
expect(Event.where(:id => other_event).count).to eq(1) |
68 | 68 |
end |
@@ -70,7 +70,7 @@ describe AgentsController do |
||
70 | 70 |
it "can only be accessed by the Agent's owner" do |
71 | 71 |
sign_in users(:jane) |
72 | 72 |
expect { |
73 |
- post :remove_events, :id => agents(:bob_website_agent).to_param |
|
73 |
+ post :remove_events, params: {:id => agents(:bob_website_agent).to_param} |
|
74 | 74 |
}.to raise_error(ActiveRecord::RecordNotFound) |
75 | 75 |
end |
76 | 76 |
end |
@@ -110,11 +110,11 @@ describe AgentsController do |
||
110 | 110 |
describe "GET show" do |
111 | 111 |
it "only shows Agents for the current user" do |
112 | 112 |
sign_in users(:bob) |
113 |
- get :show, :id => agents(:bob_website_agent).to_param |
|
113 |
+ get :show, params: {:id => agents(:bob_website_agent).to_param} |
|
114 | 114 |
expect(assigns(:agent)).to eq(agents(:bob_website_agent)) |
115 | 115 |
|
116 | 116 |
expect { |
117 |
- get :show, :id => agents(:jane_website_agent).to_param |
|
117 |
+ get :show, params: {:id => agents(:jane_website_agent).to_param} |
|
118 | 118 |
}.to raise_error(ActiveRecord::RecordNotFound) |
119 | 119 |
end |
120 | 120 |
end |
@@ -123,7 +123,7 @@ describe AgentsController do |
||
123 | 123 |
describe "with :id" do |
124 | 124 |
it "opens a clone of a given Agent" do |
125 | 125 |
sign_in users(:bob) |
126 |
- get :new, :id => agents(:bob_website_agent).to_param |
|
126 |
+ get :new, params: {:id => agents(:bob_website_agent).to_param} |
|
127 | 127 |
expect(assigns(:agent).attributes).to eq(users(:bob).agents.build_clone(agents(:bob_website_agent)).attributes) |
128 | 128 |
end |
129 | 129 |
|
@@ -131,7 +131,7 @@ describe AgentsController do |
||
131 | 131 |
sign_in users(:bob) |
132 | 132 |
|
133 | 133 |
expect { |
134 |
- get :new, :id => agents(:jane_website_agent).to_param |
|
134 |
+ get :new, params: {:id => agents(:jane_website_agent).to_param} |
|
135 | 135 |
}.to raise_error(ActiveRecord::RecordNotFound) |
136 | 136 |
end |
137 | 137 |
end |
@@ -139,13 +139,13 @@ describe AgentsController do |
||
139 | 139 |
describe "with a scenario_id" do |
140 | 140 |
it 'populates the assigned agent with the scenario' do |
141 | 141 |
sign_in users(:bob) |
142 |
- get :new, :scenario_id => scenarios(:bob_weather).id |
|
142 |
+ get :new, params: {:scenario_id => scenarios(:bob_weather).id} |
|
143 | 143 |
expect(assigns(:agent).scenario_ids).to eq([scenarios(:bob_weather).id]) |
144 | 144 |
end |
145 | 145 |
|
146 | 146 |
it "does not see other user's scenarios" do |
147 | 147 |
sign_in users(:bob) |
148 |
- get :new, :scenario_id => scenarios(:jane_weather).id |
|
148 |
+ get :new, params: {:scenario_id => scenarios(:jane_weather).id} |
|
149 | 149 |
expect(assigns(:agent).scenario_ids).to eq([]) |
150 | 150 |
end |
151 | 151 |
end |
@@ -154,11 +154,11 @@ describe AgentsController do |
||
154 | 154 |
describe "GET edit" do |
155 | 155 |
it "only shows Agents for the current user" do |
156 | 156 |
sign_in users(:bob) |
157 |
- get :edit, :id => agents(:bob_website_agent).to_param |
|
157 |
+ get :edit, params: {:id => agents(:bob_website_agent).to_param} |
|
158 | 158 |
expect(assigns(:agent)).to eq(agents(:bob_website_agent)) |
159 | 159 |
|
160 | 160 |
expect { |
161 |
- get :edit, :id => agents(:jane_website_agent).to_param |
|
161 |
+ get :edit, params: {:id => agents(:jane_website_agent).to_param} |
|
162 | 162 |
}.to raise_error(ActiveRecord::RecordNotFound) |
163 | 163 |
end |
164 | 164 |
end |
@@ -167,27 +167,27 @@ describe AgentsController do |
||
167 | 167 |
it "errors on bad types" do |
168 | 168 |
sign_in users(:bob) |
169 | 169 |
expect { |
170 |
- post :create, :agent => valid_attributes(:type => "Agents::ThisIsFake") |
|
170 |
+ post :create, params: {:agent => valid_attributes(:type => "Agents::ThisIsFake")} |
|
171 | 171 |
}.not_to change { users(:bob).agents.count } |
172 | 172 |
expect(assigns(:agent)).to be_a(Agent) |
173 | 173 |
expect(assigns(:agent)).to have(1).error_on(:type) |
174 | 174 |
|
175 | 175 |
sign_in users(:bob) |
176 | 176 |
expect { |
177 |
- post :create, :agent => valid_attributes(:type => "Object") |
|
177 |
+ post :create, params: {:agent => valid_attributes(:type => "Object")} |
|
178 | 178 |
}.not_to change { users(:bob).agents.count } |
179 | 179 |
expect(assigns(:agent)).to be_a(Agent) |
180 | 180 |
expect(assigns(:agent)).to have(1).error_on(:type) |
181 | 181 |
sign_in users(:bob) |
182 | 182 |
|
183 | 183 |
expect { |
184 |
- post :create, :agent => valid_attributes(:type => "Agent") |
|
184 |
+ post :create, params: {:agent => valid_attributes(:type => "Agent")} |
|
185 | 185 |
}.not_to change { users(:bob).agents.count } |
186 | 186 |
expect(assigns(:agent)).to be_a(Agent) |
187 | 187 |
expect(assigns(:agent)).to have(1).error_on(:type) |
188 | 188 |
|
189 | 189 |
expect { |
190 |
- post :create, :agent => valid_attributes(:type => "User") |
|
190 |
+ post :create, params: {:agent => valid_attributes(:type => "User")} |
|
191 | 191 |
}.not_to change { users(:bob).agents.count } |
192 | 192 |
expect(assigns(:agent)).to be_a(Agent) |
193 | 193 |
expect(assigns(:agent)).to have(1).error_on(:type) |
@@ -197,7 +197,7 @@ describe AgentsController do |
||
197 | 197 |
sign_in users(:bob) |
198 | 198 |
expect { |
199 | 199 |
expect { |
200 |
- post :create, :agent => valid_attributes |
|
200 |
+ post :create, params: {:agent => valid_attributes} |
|
201 | 201 |
}.to change { users(:bob).agents.count }.by(1) |
202 | 202 |
}.to change { Link.count }.by(1) |
203 | 203 |
expect(assigns(:agent)).to be_a(Agents::WebsiteAgent) |
@@ -205,11 +205,11 @@ describe AgentsController do |
||
205 | 205 |
|
206 | 206 |
it "creates Agents and accepts specifing a target agent" do |
207 | 207 |
sign_in users(:bob) |
208 |
- attributes = valid_attributes |
|
208 |
+ attributes = valid_attributes(service_id: 1) |
|
209 | 209 |
attributes[:receiver_ids] = attributes[:source_ids] |
210 | 210 |
expect { |
211 | 211 |
expect { |
212 |
- post :create, :agent => attributes |
|
212 |
+ post :create, params: {:agent => attributes} |
|
213 | 213 |
}.to change { users(:bob).agents.count }.by(1) |
214 | 214 |
}.to change { Link.count }.by(2) |
215 | 215 |
expect(assigns(:agent)).to be_a(Agents::WebsiteAgent) |
@@ -218,7 +218,7 @@ describe AgentsController do |
||
218 | 218 |
it "shows errors" do |
219 | 219 |
sign_in users(:bob) |
220 | 220 |
expect { |
221 |
- post :create, :agent => valid_attributes(:name => "") |
|
221 |
+ post :create, params: {:agent => valid_attributes(:name => "")} |
|
222 | 222 |
}.not_to change { users(:bob).agents.count } |
223 | 223 |
expect(assigns(:agent)).to have(1).errors_on(:name) |
224 | 224 |
expect(response).to render_template("new") |
@@ -228,7 +228,7 @@ describe AgentsController do |
||
228 | 228 |
sign_in users(:bob) |
229 | 229 |
expect { |
230 | 230 |
expect { |
231 |
- post :create, :agent => valid_attributes(:source_ids => [agents(:jane_weather_agent).id]) |
|
231 |
+ post :create, params: {:agent => valid_attributes(:source_ids => [agents(:jane_weather_agent).id])} |
|
232 | 232 |
}.not_to change { users(:bob).agents.count } |
233 | 233 |
}.not_to change { Link.count } |
234 | 234 |
end |
@@ -237,25 +237,25 @@ describe AgentsController do |
||
237 | 237 |
describe "PUT update" do |
238 | 238 |
it "does not allow changing types" do |
239 | 239 |
sign_in users(:bob) |
240 |
- post :update, :id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:type => "Agents::WeatherAgent") |
|
240 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:type => "Agents::WeatherAgent")} |
|
241 | 241 |
expect(assigns(:agent)).to have(1).errors_on(:type) |
242 | 242 |
expect(response).to render_template("edit") |
243 | 243 |
end |
244 | 244 |
|
245 | 245 |
it "updates attributes on Agents for the current user" do |
246 | 246 |
sign_in users(:bob) |
247 |
- post :update, :id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name") |
|
247 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name")} |
|
248 | 248 |
expect(response).to redirect_to(agents_path) |
249 | 249 |
expect(agents(:bob_website_agent).reload.name).to eq("New name") |
250 | 250 |
|
251 | 251 |
expect { |
252 |
- post :update, :id => agents(:jane_website_agent).to_param, :agent => valid_attributes(:name => "New name") |
|
252 |
+ post :update, params: {:id => agents(:jane_website_agent).to_param, :agent => valid_attributes(:name => "New name")} |
|
253 | 253 |
}.to raise_error(ActiveRecord::RecordNotFound) |
254 | 254 |
end |
255 | 255 |
|
256 | 256 |
it "accepts JSON requests" do |
257 | 257 |
sign_in users(:bob) |
258 |
- post :update, :id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name"), :format => :json |
|
258 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name")}, :format => :json |
|
259 | 259 |
expect(agents(:bob_website_agent).reload.name).to eq("New name") |
260 | 260 |
expect(JSON.parse(response.body)['name']).to eq("New name") |
261 | 261 |
expect(response).to be_success |
@@ -263,51 +263,58 @@ describe AgentsController do |
||
263 | 263 |
|
264 | 264 |
it "will not accept Agent sources owned by other users" do |
265 | 265 |
sign_in users(:bob) |
266 |
- post :update, :id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:source_ids => [agents(:jane_weather_agent).id]) |
|
266 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:source_ids => [agents(:jane_weather_agent).id])} |
|
267 | 267 |
expect(assigns(:agent)).to have(1).errors_on(:sources) |
268 | 268 |
end |
269 | 269 |
|
270 | 270 |
it "will not accept Scenarios owned by other users" do |
271 | 271 |
sign_in users(:bob) |
272 |
- post :update, :id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:scenario_ids => [scenarios(:jane_weather).id]) |
|
272 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:scenario_ids => [scenarios(:jane_weather).id])} |
|
273 | 273 |
expect(assigns(:agent)).to have(1).errors_on(:scenarios) |
274 | 274 |
end |
275 | 275 |
|
276 | 276 |
it "shows errors" do |
277 | 277 |
sign_in users(:bob) |
278 |
- post :update, :id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "") |
|
278 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "")} |
|
279 | 279 |
expect(assigns(:agent)).to have(1).errors_on(:name) |
280 | 280 |
expect(response).to render_template("edit") |
281 | 281 |
end |
282 | 282 |
|
283 |
+ it 'does not allow to modify the agents user_id' do |
|
284 |
+ sign_in users(:bob) |
|
285 |
+ expect { |
|
286 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:user_id => users(:jane).id)} |
|
287 |
+ }.to raise_error(ActionController::UnpermittedParameters) |
|
288 |
+ end |
|
289 |
+ |
|
283 | 290 |
describe "redirecting back" do |
284 | 291 |
before do |
285 | 292 |
sign_in users(:bob) |
286 | 293 |
end |
287 | 294 |
|
288 | 295 |
it "can redirect back to the show path" do |
289 |
- post :update, :id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name"), :return => "show" |
|
296 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name"), :return => "show"} |
|
290 | 297 |
expect(response).to redirect_to(agent_path(agents(:bob_website_agent))) |
291 | 298 |
end |
292 | 299 |
|
293 | 300 |
it "redirect back to the index path by default" do |
294 |
- post :update, :id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name") |
|
301 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name")} |
|
295 | 302 |
expect(response).to redirect_to(agents_path) |
296 | 303 |
end |
297 | 304 |
|
298 | 305 |
it "accepts return paths to scenarios" do |
299 |
- post :update, :id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name"), :return => "/scenarios/2" |
|
306 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name"), :return => "/scenarios/2"} |
|
300 | 307 |
expect(response).to redirect_to("/scenarios/2") |
301 | 308 |
end |
302 | 309 |
|
303 | 310 |
it "sanitizes return paths" do |
304 |
- post :update, :id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name"), :return => "/scenar" |
|
311 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name"), :return => "/scenar"} |
|
305 | 312 |
expect(response).to redirect_to(agents_path) |
306 | 313 |
|
307 |
- post :update, :id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name"), :return => "http://google.com" |
|
314 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name"), :return => "http://google.com"} |
|
308 | 315 |
expect(response).to redirect_to(agents_path) |
309 | 316 |
|
310 |
- post :update, :id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name"), :return => "javascript:alert(1)" |
|
317 |
+ post :update, params: {:id => agents(:bob_website_agent).to_param, :agent => valid_attributes(:name => "New name"), :return => "javascript:alert(1)"} |
|
311 | 318 |
expect(response).to redirect_to(agents_path) |
312 | 319 |
end |
313 | 320 |
end |
@@ -318,7 +325,7 @@ describe AgentsController do |
||
318 | 325 |
agent.disabled = true |
319 | 326 |
agent.last_checked_event_id = nil |
320 | 327 |
agent.save! |
321 |
- post :update, id: agents(:bob_website_agent).to_param, agent: { disabled: 'false', drop_pending_events: 'true' } |
|
328 |
+ post :update, params: {id: agents(:bob_website_agent).to_param, agent: { disabled: 'false', drop_pending_events: 'true' }} |
|
322 | 329 |
agent.reload |
323 | 330 |
expect(agent.disabled).to eq(false) |
324 | 331 |
expect(agent.last_checked_event_id).to eq(Event.maximum(:id)) |
@@ -330,13 +337,13 @@ describe AgentsController do |
||
330 | 337 |
sign_in users(:bob) |
331 | 338 |
|
332 | 339 |
expect(agents(:bob_weather_agent).scenarios).to include(scenarios(:bob_weather)) |
333 |
- put :leave_scenario, :id => agents(:bob_weather_agent).to_param, :scenario_id => scenarios(:bob_weather).to_param |
|
340 |
+ put :leave_scenario, params: {:id => agents(:bob_weather_agent).to_param, :scenario_id => scenarios(:bob_weather).to_param} |
|
334 | 341 |
expect(agents(:bob_weather_agent).scenarios).not_to include(scenarios(:bob_weather)) |
335 | 342 |
|
336 | 343 |
expect(Scenario.where(:id => scenarios(:bob_weather).id)).to exist |
337 | 344 |
|
338 | 345 |
expect { |
339 |
- put :leave_scenario, :id => agents(:jane_weather_agent).to_param, :scenario_id => scenarios(:jane_weather).to_param |
|
346 |
+ put :leave_scenario, params: {:id => agents(:jane_weather_agent).to_param, :scenario_id => scenarios(:jane_weather).to_param} |
|
340 | 347 |
}.to raise_error(ActiveRecord::RecordNotFound) |
341 | 348 |
end |
342 | 349 |
end |
@@ -345,25 +352,25 @@ describe AgentsController do |
||
345 | 352 |
it "destroys only Agents owned by the current user" do |
346 | 353 |
sign_in users(:bob) |
347 | 354 |
expect { |
348 |
- delete :destroy, :id => agents(:bob_website_agent).to_param |
|
355 |
+ delete :destroy, params: {:id => agents(:bob_website_agent).to_param} |
|
349 | 356 |
}.to change(Agent, :count).by(-1) |
350 | 357 |
|
351 | 358 |
expect { |
352 |
- delete :destroy, :id => agents(:jane_website_agent).to_param |
|
359 |
+ delete :destroy, params: {:id => agents(:jane_website_agent).to_param} |
|
353 | 360 |
}.to raise_error(ActiveRecord::RecordNotFound) |
354 | 361 |
end |
355 | 362 |
|
356 | 363 |
it "redirects correctly when the Agent is deleted from the Agent itself" do |
357 | 364 |
sign_in users(:bob) |
358 | 365 |
|
359 |
- delete :destroy, :id => agents(:bob_website_agent).to_param |
|
366 |
+ delete :destroy, params: {:id => agents(:bob_website_agent).to_param} |
|
360 | 367 |
expect(response).to redirect_to agents_path |
361 | 368 |
end |
362 | 369 |
|
363 | 370 |
it "redirects correctly when the Agent is deleted from a Scenario" do |
364 | 371 |
sign_in users(:bob) |
365 | 372 |
|
366 |
- delete :destroy, :id => agents(:bob_weather_agent).to_param, :return => scenario_path(scenarios(:bob_weather)).to_param |
|
373 |
+ delete :destroy, params: {:id => agents(:bob_weather_agent).to_param, :return => scenario_path(scenarios(:bob_weather)).to_param} |
|
367 | 374 |
expect(response).to redirect_to scenario_path(scenarios(:bob_weather)) |
368 | 375 |
end |
369 | 376 |
end |
@@ -380,7 +387,7 @@ describe AgentsController do |
||
380 | 387 |
stub(klass).validate_option { true } |
381 | 388 |
end |
382 | 389 |
|
383 |
- post :validate, @params |
|
390 |
+ post :validate, params: @params |
|
384 | 391 |
expect(response.status).to eq 200 |
385 | 392 |
end |
386 | 393 |
|
@@ -389,7 +396,7 @@ describe AgentsController do |
||
389 | 396 |
stub(klass).validate_option { false } |
390 | 397 |
end |
391 | 398 |
|
392 |
- post :validate, @params |
|
399 |
+ post :validate, params: @params |
|
393 | 400 |
expect(response.status).to eq 403 |
394 | 401 |
end |
395 | 402 |
end |
@@ -400,7 +407,7 @@ describe AgentsController do |
||
400 | 407 |
stub(klass).complete_option { [{name: 'test', value: 1}] } |
401 | 408 |
end |
402 | 409 |
|
403 |
- post :complete, @params |
|
410 |
+ post :complete, params: @params |
|
404 | 411 |
expect(response.status).to eq 200 |
405 | 412 |
expect(response.header['Content-Type']).to include('application/json') |
406 | 413 |
|
@@ -413,7 +420,7 @@ describe AgentsController do |
||
413 | 420 |
agent = agents(:bob_website_agent) |
414 | 421 |
agent.update!(memory: { "test" => 42 }) |
415 | 422 |
sign_in users(:bob) |
416 |
- delete :destroy_memory, id: agent.to_param |
|
423 |
+ delete :destroy_memory, params: {id: agent.to_param} |
|
417 | 424 |
expect(agent.reload.memory).to eq({}) |
418 | 425 |
end |
419 | 426 |
|
@@ -422,9 +429,23 @@ describe AgentsController do |
||
422 | 429 |
agent.update!(memory: { "test" => 42 }) |
423 | 430 |
sign_in users(:bob) |
424 | 431 |
expect { |
425 |
- delete :destroy_memory, id: agent.to_param |
|
432 |
+ delete :destroy_memory, params: {id: agent.to_param} |
|
426 | 433 |
}.to raise_error(ActiveRecord::RecordNotFound) |
427 | 434 |
expect(agent.reload.memory).to eq({ "test" => 42}) |
428 | 435 |
end |
429 | 436 |
end |
437 |
+ |
|
438 |
+ describe 'DELETE undefined' do |
|
439 |
+ it 'removes an undefined agent from the database' do |
|
440 |
+ sign_in users(:bob) |
|
441 |
+ agent = agents(:bob_website_agent) |
|
442 |
+ agent.update_attribute(:type, 'Agents::UndefinedAgent') |
|
443 |
+ agent2 = agents(:jane_website_agent) |
|
444 |
+ agent2.update_attribute(:type, 'Agents::UndefinedAgent') |
|
445 |
+ |
|
446 |
+ expect { |
|
447 |
+ delete :destroy_undefined |
|
448 |
+ }.to change { Agent.count }.by(-1) |
|
449 |
+ end |
|
450 |
+ end |
|
430 | 451 |
end |
@@ -15,12 +15,12 @@ describe EventsController do |
||
15 | 15 |
|
16 | 16 |
it "can filter by Agent" do |
17 | 17 |
sign_in users(:bob) |
18 |
- get :index, :agent_id => agents(:bob_website_agent) |
|
18 |
+ get :index, params: {:agent_id => agents(:bob_website_agent)} |
|
19 | 19 |
expect(assigns(:events).length).to eq(agents(:bob_website_agent).events.length) |
20 | 20 |
expect(assigns(:events).all? {|i| expect(i.agent).to eq(agents(:bob_website_agent)) }).to be_truthy |
21 | 21 |
|
22 | 22 |
expect { |
23 |
- get :index, :agent_id => agents(:jane_website_agent) |
|
23 |
+ get :index, params: {:agent_id => agents(:jane_website_agent)} |
|
24 | 24 |
}.to raise_error(ActiveRecord::RecordNotFound) |
25 | 25 |
end |
26 | 26 |
end |
@@ -28,11 +28,11 @@ describe EventsController do |
||
28 | 28 |
describe "GET show" do |
29 | 29 |
it "only shows Events for the current user" do |
30 | 30 |
sign_in users(:bob) |
31 |
- get :show, :id => events(:bob_website_agent_event).to_param |
|
31 |
+ get :show, params: {:id => events(:bob_website_agent_event).to_param} |
|
32 | 32 |
expect(assigns(:event)).to eq(events(:bob_website_agent_event)) |
33 | 33 |
|
34 | 34 |
expect { |
35 |
- get :show, :id => events(:jane_website_agent_event).to_param |
|
35 |
+ get :show, params: {:id => events(:jane_website_agent_event).to_param} |
|
36 | 36 |
}.to raise_error(ActiveRecord::RecordNotFound) |
37 | 37 |
end |
38 | 38 |
end |
@@ -45,7 +45,7 @@ describe EventsController do |
||
45 | 45 |
|
46 | 46 |
it "clones and re-emits events" do |
47 | 47 |
expect { |
48 |
- post :reemit, :id => events(:bob_website_agent_event).to_param |
|
48 |
+ post :reemit, params: {:id => events(:bob_website_agent_event).to_param} |
|
49 | 49 |
}.to change { Event.count }.by(1) |
50 | 50 |
expect(Event.last.payload).to eq(events(:bob_website_agent_event).payload) |
51 | 51 |
expect(Event.last.agent).to eq(events(:bob_website_agent_event).agent) |
@@ -54,7 +54,7 @@ describe EventsController do |
||
54 | 54 |
|
55 | 55 |
it "can only re-emit Events for the current user" do |
56 | 56 |
expect { |
57 |
- post :reemit, :id => events(:jane_website_agent_event).to_param |
|
57 |
+ post :reemit, params: {:id => events(:jane_website_agent_event).to_param} |
|
58 | 58 |
}.to raise_error(ActiveRecord::RecordNotFound) |
59 | 59 |
end |
60 | 60 |
end |
@@ -63,11 +63,11 @@ describe EventsController do |
||
63 | 63 |
it "only deletes events for the current user" do |
64 | 64 |
sign_in users(:bob) |
65 | 65 |
expect { |
66 |
- delete :destroy, :id => events(:bob_website_agent_event).to_param |
|
66 |
+ delete :destroy, params: {:id => events(:bob_website_agent_event).to_param} |
|
67 | 67 |
}.to change { Event.count }.by(-1) |
68 | 68 |
|
69 | 69 |
expect { |
70 |
- delete :destroy, :id => events(:jane_website_agent_event).to_param |
|
70 |
+ delete :destroy, params: {:id => events(:jane_website_agent_event).to_param} |
|
71 | 71 |
}.to raise_error(ActiveRecord::RecordNotFound) |
72 | 72 |
end |
73 | 73 |
end |
@@ -37,11 +37,11 @@ describe JobsController do |
||
37 | 37 |
end |
38 | 38 |
|
39 | 39 |
it "destroy a job which is not running" do |
40 |
- expect { delete :destroy, id: @not_running.id }.to change(Delayed::Job, :count).by(-1) |
|
40 |
+ expect { delete :destroy, params: {id: @not_running.id} }.to change(Delayed::Job, :count).by(-1) |
|
41 | 41 |
end |
42 | 42 |
|
43 | 43 |
it "does not destroy a running job" do |
44 |
- expect { delete :destroy, id: @running.id }.to change(Delayed::Job, :count).by(0) |
|
44 |
+ expect { delete :destroy, params: {id: @running.id} }.to change(Delayed::Job, :count).by(0) |
|
45 | 45 |
end |
46 | 46 |
end |
47 | 47 |
|
@@ -54,15 +54,15 @@ describe JobsController do |
||
54 | 54 |
end |
55 | 55 |
|
56 | 56 |
it "queue a job which is not running" do |
57 |
- expect { put :run, id: @not_running.id }.to change { @not_running.reload.run_at } |
|
57 |
+ expect { put :run, params: {id: @not_running.id} }.to change { @not_running.reload.run_at } |
|
58 | 58 |
end |
59 | 59 |
|
60 | 60 |
it "queue a job that failed" do |
61 |
- expect { put :run, id: @failed.id }.to change { @failed.reload.run_at } |
|
61 |
+ expect { put :run, params: {id: @failed.id} }.to change { @failed.reload.run_at } |
|
62 | 62 |
end |
63 | 63 |
|
64 | 64 |
it "not queue a running job" do |
65 |
- expect { put :run, id: @running.id }.not_to change { @not_running.reload.run_at } |
|
65 |
+ expect { put :run, params: {id: @running.id} }.not_to change { @not_running.reload.run_at } |
|
66 | 66 |
end |
67 | 67 |
end |
68 | 68 |
|
@@ -4,7 +4,7 @@ describe LogsController do |
||
4 | 4 |
describe "GET index" do |
5 | 5 |
it "can filter by Agent" do |
6 | 6 |
sign_in users(:bob) |
7 |
- get :index, :agent_id => agents(:bob_weather_agent).id |
|
7 |
+ get :index, params: {:agent_id => agents(:bob_weather_agent).id} |
|
8 | 8 |
expect(assigns(:logs).length).to eq(agents(:bob_weather_agent).logs.length) |
9 | 9 |
expect(assigns(:logs).all? {|i| expect(i.agent).to eq(agents(:bob_weather_agent)) }).to be_truthy |
10 | 10 |
end |
@@ -12,7 +12,7 @@ describe LogsController do |
||
12 | 12 |
it "only loads Agents owned by the current user" do |
13 | 13 |
sign_in users(:bob) |
14 | 14 |
expect { |
15 |
- get :index, :agent_id => agents(:jane_weather_agent).id |
|
15 |
+ get :index, params: {:agent_id => agents(:jane_weather_agent).id} |
|
16 | 16 |
}.to raise_error(ActiveRecord::RecordNotFound) |
17 | 17 |
end |
18 | 18 |
end |
@@ -22,7 +22,7 @@ describe LogsController do |
||
22 | 22 |
agents(:bob_weather_agent).last_error_log_at = 2.hours.ago |
23 | 23 |
sign_in users(:bob) |
24 | 24 |
expect { |
25 |
- delete :clear, :agent_id => agents(:bob_weather_agent).id |
|
25 |
+ delete :clear, params: {:agent_id => agents(:bob_weather_agent).id} |
|
26 | 26 |
}.to change { AgentLog.count }.by(-1 * agents(:bob_weather_agent).logs.count) |
27 | 27 |
expect(assigns(:logs).length).to eq(0) |
28 | 28 |
expect(agents(:bob_weather_agent).reload.logs.count).to eq(0) |
@@ -32,7 +32,7 @@ describe LogsController do |
||
32 | 32 |
it "only deletes logs for an Agent owned by the current user" do |
33 | 33 |
sign_in users(:bob) |
34 | 34 |
expect { |
35 |
- delete :clear, :agent_id => agents(:jane_weather_agent).id |
|
35 |
+ delete :clear, params: {:agent_id => agents(:jane_weather_agent).id} |
|
36 | 36 |
}.to raise_error(ActiveRecord::RecordNotFound) |
37 | 37 |
end |
38 | 38 |
end |
@@ -5,22 +5,23 @@ describe OmniauthCallbacksController do |
||
5 | 5 |
sign_in users(:bob) |
6 | 6 |
OmniAuth.config.test_mode = true |
7 | 7 |
request.env["devise.mapping"] = Devise.mappings[:user] |
8 |
- request.env["omniauth.auth"] = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/twitter.json'))) |
|
9 | 8 |
end |
10 | 9 |
|
11 | 10 |
describe "accepting a callback url" do |
12 | 11 |
it "should update the user's credentials" do |
12 |
+ request.env["omniauth.auth"] = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/twitter.json'))) |
|
13 | 13 |
expect { |
14 | 14 |
get :twitter |
15 | 15 |
}.to change { users(:bob).services.count }.by(1) |
16 | 16 |
end |
17 |
+ end |
|
17 | 18 |
|
18 |
- # it "should work with an unknown provider (for now)" do |
|
19 |
- # request.env["omniauth.auth"]['provider'] = 'unknown' |
|
20 |
- # expect { |
|
21 |
- # get :unknown |
|
22 |
- # }.to change { users(:bob).services.count }.by(1) |
|
23 |
- # expect(users(:bob).services.first.provider).to eq('unknown') |
|
24 |
- # end |
|
19 |
+ describe "handling a provider with non-standard omniauth options" do |
|
20 |
+ it "should update the user's credentials" do |
|
21 |
+ request.env["omniauth.auth"] = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/37signals.json'))) |
|
22 |
+ expect { |
|
23 |
+ get "37signals" |
|
24 |
+ }.to change { users(:bob).services.count }.by(1) |
|
25 |
+ end |
|
25 | 26 |
end |
26 | 27 |
end |
@@ -15,7 +15,7 @@ describe ScenarioImportsController do |
||
15 | 15 |
|
16 | 16 |
describe "POST create" do |
17 | 17 |
it "initializes a ScenarioImport for current_user, passing in params" do |
18 |
- post :create, :scenario_import => { :url => "bad url" } |
|
18 |
+ post :create, params: {:scenario_import => { :url => "bad url" }} |
|
19 | 19 |
expect(assigns(:scenario_import).user).to eq(users(:bob)) |
20 | 20 |
expect(assigns(:scenario_import).url).to eq("bad url") |
21 | 21 |
expect(assigns(:scenario_import)).not_to be_valid |
@@ -18,34 +18,34 @@ describe ScenariosController do |
||
18 | 18 |
|
19 | 19 |
describe "GET show" do |
20 | 20 |
it "only shows Scenarios for the current user" do |
21 |
- get :show, :id => scenarios(:bob_weather).to_param |
|
21 |
+ get :show, params: {:id => scenarios(:bob_weather).to_param} |
|
22 | 22 |
expect(assigns(:scenario)).to eq(scenarios(:bob_weather)) |
23 | 23 |
|
24 | 24 |
expect { |
25 |
- get :show, :id => scenarios(:jane_weather).to_param |
|
25 |
+ get :show, params: {:id => scenarios(:jane_weather).to_param} |
|
26 | 26 |
}.to raise_error(ActiveRecord::RecordNotFound) |
27 | 27 |
end |
28 | 28 |
|
29 | 29 |
it "loads Agents for the requested Scenario" do |
30 |
- get :show, :id => scenarios(:bob_weather).to_param |
|
30 |
+ get :show, params: {:id => scenarios(:bob_weather).to_param} |
|
31 | 31 |
expect(assigns(:agents).pluck(:id).sort).to eq(scenarios(:bob_weather).agents.pluck(:id).sort) |
32 | 32 |
end |
33 | 33 |
end |
34 | 34 |
|
35 | 35 |
describe "GET share" do |
36 | 36 |
it "only displays Scenario share information for the current user" do |
37 |
- get :share, :id => scenarios(:bob_weather).to_param |
|
37 |
+ get :share, params: {:id => scenarios(:bob_weather).to_param} |
|
38 | 38 |
expect(assigns(:scenario)).to eq(scenarios(:bob_weather)) |
39 | 39 |
|
40 | 40 |
expect { |
41 |
- get :share, :id => scenarios(:jane_weather).to_param |
|
41 |
+ get :share, params: {:id => scenarios(:jane_weather).to_param} |
|
42 | 42 |
}.to raise_error(ActiveRecord::RecordNotFound) |
43 | 43 |
end |
44 | 44 |
end |
45 | 45 |
|
46 | 46 |
describe "GET export" do |
47 | 47 |
it "returns a JSON file download from an instantiated AgentsExporter" do |
48 |
- get :export, :id => scenarios(:bob_weather).to_param |
|
48 |
+ get :export, params: {:id => scenarios(:bob_weather).to_param} |
|
49 | 49 |
expect(assigns(:exporter).options[:name]).to eq(scenarios(:bob_weather).name) |
50 | 50 |
expect(assigns(:exporter).options[:description]).to eq(scenarios(:bob_weather).description) |
51 | 51 |
expect(assigns(:exporter).options[:agents]).to eq(scenarios(:bob_weather).agents) |
@@ -59,11 +59,11 @@ describe ScenariosController do |
||
59 | 59 |
end |
60 | 60 |
|
61 | 61 |
it "only exports private Scenarios for the current user" do |
62 |
- get :export, :id => scenarios(:bob_weather).to_param |
|
62 |
+ get :export, params: {:id => scenarios(:bob_weather).to_param} |
|
63 | 63 |
expect(assigns(:scenario)).to eq(scenarios(:bob_weather)) |
64 | 64 |
|
65 | 65 |
expect { |
66 |
- get :export, :id => scenarios(:jane_weather).to_param |
|
66 |
+ get :export, params: {:id => scenarios(:jane_weather).to_param} |
|
67 | 67 |
}.to raise_error(ActiveRecord::RecordNotFound) |
68 | 68 |
end |
69 | 69 |
|
@@ -73,14 +73,14 @@ describe ScenariosController do |
||
73 | 73 |
end |
74 | 74 |
|
75 | 75 |
it "exports public scenarios for other users when logged in" do |
76 |
- get :export, :id => scenarios(:jane_weather).to_param |
|
76 |
+ get :export, params: {:id => scenarios(:jane_weather).to_param} |
|
77 | 77 |
expect(assigns(:scenario)).to eq(scenarios(:jane_weather)) |
78 | 78 |
expect(assigns(:exporter).options[:source_url]).to eq(export_scenario_url(scenarios(:jane_weather))) |
79 | 79 |
end |
80 | 80 |
|
81 | 81 |
it "exports public scenarios for other users when logged out" do |
82 | 82 |
sign_out :user |
83 |
- get :export, :id => scenarios(:jane_weather).to_param |
|
83 |
+ get :export, params: {:id => scenarios(:jane_weather).to_param} |
|
84 | 84 |
expect(assigns(:scenario)).to eq(scenarios(:jane_weather)) |
85 | 85 |
expect(assigns(:exporter).options[:source_url]).to eq(export_scenario_url(scenarios(:jane_weather))) |
86 | 86 |
end |
@@ -89,11 +89,11 @@ describe ScenariosController do |
||
89 | 89 |
|
90 | 90 |
describe "GET edit" do |
91 | 91 |
it "only shows Scenarios for the current user" do |
92 |
- get :edit, :id => scenarios(:bob_weather).to_param |
|
92 |
+ get :edit, params: {:id => scenarios(:bob_weather).to_param} |
|
93 | 93 |
expect(assigns(:scenario)).to eq(scenarios(:bob_weather)) |
94 | 94 |
|
95 | 95 |
expect { |
96 |
- get :edit, :id => scenarios(:jane_weather).to_param |
|
96 |
+ get :edit, params: {:id => scenarios(:jane_weather).to_param} |
|
97 | 97 |
}.to raise_error(ActiveRecord::RecordNotFound) |
98 | 98 |
end |
99 | 99 |
end |
@@ -101,13 +101,13 @@ describe ScenariosController do |
||
101 | 101 |
describe "POST create" do |
102 | 102 |
it "creates Scenarios for the current user" do |
103 | 103 |
expect { |
104 |
- post :create, :scenario => valid_attributes |
|
104 |
+ post :create, params: {:scenario => valid_attributes} |
|
105 | 105 |
}.to change { users(:bob).scenarios.count }.by(1) |
106 | 106 |
end |
107 | 107 |
|
108 | 108 |
it "shows errors" do |
109 | 109 |
expect { |
110 |
- post :create, :scenario => valid_attributes(:name => "") |
|
110 |
+ post :create, params: {:scenario => valid_attributes(:name => "")} |
|
111 | 111 |
}.not_to change { users(:bob).scenarios.count } |
112 | 112 |
expect(assigns(:scenario)).to have(1).errors_on(:name) |
113 | 113 |
expect(response).to render_template("new") |
@@ -115,35 +115,41 @@ describe ScenariosController do |
||
115 | 115 |
|
116 | 116 |
it "will not create Scenarios for other users" do |
117 | 117 |
expect { |
118 |
- post :create, :scenario => valid_attributes(:user_id => users(:jane).id) |
|
119 |
- }.to raise_error(ActiveModel::MassAssignmentSecurity::Error) |
|
118 |
+ post :create, params: {:scenario => valid_attributes(:user_id => users(:jane).id)} |
|
119 |
+ }.to raise_error(ActionController::UnpermittedParameters) |
|
120 | 120 |
end |
121 | 121 |
end |
122 | 122 |
|
123 | 123 |
describe "PUT update" do |
124 | 124 |
it "updates attributes on Scenarios for the current user" do |
125 |
- post :update, :id => scenarios(:bob_weather).to_param, :scenario => { :name => "new_name", :public => "1" } |
|
125 |
+ post :update, params: {:id => scenarios(:bob_weather).to_param, :scenario => { :name => "new_name", :public => "1" }} |
|
126 | 126 |
expect(response).to redirect_to(scenario_path(scenarios(:bob_weather))) |
127 | 127 |
expect(scenarios(:bob_weather).reload.name).to eq("new_name") |
128 | 128 |
expect(scenarios(:bob_weather)).to be_public |
129 | 129 |
|
130 | 130 |
expect { |
131 |
- post :update, :id => scenarios(:jane_weather).to_param, :scenario => { :name => "new_name" } |
|
131 |
+ post :update, params: {:id => scenarios(:jane_weather).to_param, :scenario => { :name => "new_name" }} |
|
132 | 132 |
}.to raise_error(ActiveRecord::RecordNotFound) |
133 | 133 |
expect(scenarios(:jane_weather).reload.name).not_to eq("new_name") |
134 | 134 |
end |
135 | 135 |
|
136 | 136 |
it "shows errors" do |
137 |
- post :update, :id => scenarios(:bob_weather).to_param, :scenario => { :name => "" } |
|
137 |
+ post :update, params: {:id => scenarios(:bob_weather).to_param, :scenario => { :name => "" }} |
|
138 | 138 |
expect(assigns(:scenario)).to have(1).errors_on(:name) |
139 | 139 |
expect(response).to render_template("edit") |
140 | 140 |
end |
141 |
+ |
|
142 |
+ it 'adds an agent to the scenario' do |
|
143 |
+ expect { |
|
144 |
+ post :update, params: {:id => scenarios(:bob_weather).to_param, :scenario => { :name => "new_name", :public => "1", agent_ids: scenarios(:bob_weather).agent_ids + [agents(:bob_website_agent).id] }} |
|
145 |
+ }.to change { scenarios(:bob_weather).reload.agent_ids.length }.by(1) |
|
146 |
+ end |
|
141 | 147 |
end |
142 | 148 |
|
143 | 149 |
describe 'PUT enable_or_disable_all_agents' do |
144 | 150 |
it 'updates disabled on all agents in a scenario for the current user' do |
145 | 151 |
@params = {"scenario"=>{"disabled"=>"true"}, "commit"=>"Yes", "id"=> scenarios(:bob_weather).id} |
146 |
- put :enable_or_disable_all_agents, @params |
|
152 |
+ put :enable_or_disable_all_agents, params: @params |
|
147 | 153 |
expect(agents(:bob_rain_notifier_agent).disabled).to eq(true) |
148 | 154 |
expect(response).to redirect_to(scenario_path(scenarios(:bob_weather))) |
149 | 155 |
end |
@@ -152,17 +158,17 @@ describe ScenariosController do |
||
152 | 158 |
describe "DELETE destroy" do |
153 | 159 |
it "destroys only Scenarios owned by the current user" do |
154 | 160 |
expect { |
155 |
- delete :destroy, :id => scenarios(:bob_weather).to_param |
|
161 |
+ delete :destroy, params: {:id => scenarios(:bob_weather).to_param} |
|
156 | 162 |
}.to change(Scenario, :count).by(-1) |
157 | 163 |
|
158 | 164 |
expect { |
159 |
- delete :destroy, :id => scenarios(:jane_weather).to_param |
|
165 |
+ delete :destroy, params: {:id => scenarios(:jane_weather).to_param} |
|
160 | 166 |
}.to raise_error(ActiveRecord::RecordNotFound) |
161 | 167 |
end |
162 | 168 |
|
163 | 169 |
it "passes the mode to the model" do |
164 | 170 |
expect { |
165 |
- delete :destroy, id: scenarios(:bob_weather).to_param, mode: 'all_agents' |
|
171 |
+ delete :destroy, params: {id: scenarios(:bob_weather).to_param, mode: 'all_agents'} |
|
166 | 172 |
}.to change(Agent, :count).by(-2) |
167 | 173 |
end |
168 | 174 |
end |
@@ -14,14 +14,14 @@ describe ServicesController do |
||
14 | 14 |
|
15 | 15 |
describe "POST toggle_availability" do |
16 | 16 |
it "should work for service of the user" do |
17 |
- post :toggle_availability, :id => services(:generic).to_param |
|
17 |
+ post :toggle_availability, params: {:id => services(:generic).to_param} |
|
18 | 18 |
expect(assigns(:service)).to eq(services(:generic)) |
19 | 19 |
redirect_to(services_path) |
20 | 20 |
end |
21 | 21 |
|
22 | 22 |
it "should not work for a service of another user" do |
23 | 23 |
expect { |
24 |
- post :toggle_availability, :id => services(:global).to_param |
|
24 |
+ post :toggle_availability, params: {:id => services(:global).to_param} |
|
25 | 25 |
}.to raise_error(ActiveRecord::RecordNotFound) |
26 | 26 |
end |
27 | 27 |
end |
@@ -29,11 +29,11 @@ describe ServicesController do |
||
29 | 29 |
describe "DELETE destroy" do |
30 | 30 |
it "destroys only services owned by the current user" do |
31 | 31 |
expect { |
32 |
- delete :destroy, :id => services(:generic).to_param |
|
32 |
+ delete :destroy, params: {:id => services(:generic).to_param} |
|
33 | 33 |
}.to change(Service, :count).by(-1) |
34 | 34 |
|
35 | 35 |
expect { |
36 |
- delete :destroy, :id => services(:global).to_param |
|
36 |
+ delete :destroy, params: {:id => services(:global).to_param} |
|
37 | 37 |
}.to raise_error(ActiveRecord::RecordNotFound) |
38 | 38 |
end |
39 | 39 |
end |
@@ -22,30 +22,30 @@ describe UserCredentialsController do |
||
22 | 22 |
|
23 | 23 |
describe "GET edit" do |
24 | 24 |
it "only shows UserCredentials for the current user" do |
25 |
- get :edit, :id => user_credentials(:bob_aws_secret).to_param |
|
25 |
+ get :edit, params: {:id => user_credentials(:bob_aws_secret).to_param} |
|
26 | 26 |
expect(assigns(:user_credential)).to eq(user_credentials(:bob_aws_secret)) |
27 | 27 |
|
28 | 28 |
expect { |
29 |
- get :edit, :id => user_credentials(:jane_aws_secret).to_param |
|
29 |
+ get :edit, params: {:id => user_credentials(:jane_aws_secret).to_param} |
|
30 | 30 |
}.to raise_error(ActiveRecord::RecordNotFound) |
31 | 31 |
end |
32 | 32 |
end |
33 | 33 |
|
34 | 34 |
describe "Post import" do |
35 | 35 |
it "asserts user credentials were created for current user only" do |
36 |
- post :import, :file => @file |
|
36 |
+ post :import, params: {:file => @file} |
|
37 | 37 |
expect(controller.current_user.id).to eq(users(:bob).id) |
38 | 38 |
expect(controller.current_user.user_credentials).to eq(users(:bob).user_credentials) |
39 | 39 |
end |
40 | 40 |
|
41 | 41 |
it "asserts that primary id in json file is ignored" do |
42 |
- post :import, :file => @file |
|
42 |
+ post :import, params: {:file => @file} |
|
43 | 43 |
expect(controller.current_user.user_credentials.last.id).not_to eq(24) |
44 | 44 |
end |
45 | 45 |
|
46 | 46 |
it "duplicate credential name shows an error that it is not saved" do |
47 | 47 |
file1 = fixture_file_upload('multiple_user_credentials.json') |
48 |
- post :import, :file => file1 |
|
48 |
+ post :import, params: {:file => file1} |
|
49 | 49 |
expect(flash[:notice]).to eq("One or more of the uploaded credentials was not imported due to an error. Perhaps an existing credential had the same name?") |
50 | 50 |
expect(response).to redirect_to(user_credentials_path) |
51 | 51 |
end |
@@ -54,13 +54,13 @@ describe UserCredentialsController do |
||
54 | 54 |
describe "POST create" do |
55 | 55 |
it "creates UserCredentials for the current user" do |
56 | 56 |
expect { |
57 |
- post :create, :user_credential => valid_attributes |
|
57 |
+ post :create, params: {:user_credential => valid_attributes} |
|
58 | 58 |
}.to change { users(:bob).user_credentials.count }.by(1) |
59 | 59 |
end |
60 | 60 |
|
61 | 61 |
it "shows errors" do |
62 | 62 |
expect { |
63 |
- post :create, :user_credential => valid_attributes(:credential_name => "") |
|
63 |
+ post :create, params: {:user_credential => valid_attributes(:credential_name => "")} |
|
64 | 64 |
}.not_to change { users(:bob).user_credentials.count } |
65 | 65 |
expect(assigns(:user_credential)).to have(1).errors_on(:credential_name) |
66 | 66 |
expect(response).to render_template("new") |
@@ -68,25 +68,25 @@ describe UserCredentialsController do |
||
68 | 68 |
|
69 | 69 |
it "will not create UserCredentials for other users" do |
70 | 70 |
expect { |
71 |
- post :create, :user_credential => valid_attributes(:user_id => users(:jane).id) |
|
72 |
- }.to raise_error(ActiveModel::MassAssignmentSecurity::Error) |
|
71 |
+ post :create, params: {:user_credential => valid_attributes(:user_id => users(:jane).id)} |
|
72 |
+ }.to raise_error(ActionController::UnpermittedParameters) |
|
73 | 73 |
end |
74 | 74 |
end |
75 | 75 |
|
76 | 76 |
describe "PUT update" do |
77 | 77 |
it "updates attributes on UserCredentials for the current user" do |
78 |
- post :update, :id => user_credentials(:bob_aws_key).to_param, :user_credential => { :credential_name => "new_name" } |
|
78 |
+ post :update, params: {:id => user_credentials(:bob_aws_key).to_param, :user_credential => { :credential_name => "new_name" }} |
|
79 | 79 |
expect(response).to redirect_to(user_credentials_path) |
80 | 80 |
expect(user_credentials(:bob_aws_key).reload.credential_name).to eq("new_name") |
81 | 81 |
|
82 | 82 |
expect { |
83 |
- post :update, :id => user_credentials(:jane_aws_key).to_param, :user_credential => { :credential_name => "new_name" } |
|
83 |
+ post :update, params: {:id => user_credentials(:jane_aws_key).to_param, :user_credential => { :credential_name => "new_name" }} |
|
84 | 84 |
}.to raise_error(ActiveRecord::RecordNotFound) |
85 | 85 |
expect(user_credentials(:jane_aws_key).reload.credential_name).not_to eq("new_name") |
86 | 86 |
end |
87 | 87 |
|
88 | 88 |
it "shows errors" do |
89 |
- post :update, :id => user_credentials(:bob_aws_key).to_param, :user_credential => { :credential_name => "" } |
|
89 |
+ post :update, params: {:id => user_credentials(:bob_aws_key).to_param, :user_credential => { :credential_name => "" }} |
|
90 | 90 |
expect(assigns(:user_credential)).to have(1).errors_on(:credential_name) |
91 | 91 |
expect(response).to render_template("edit") |
92 | 92 |
end |
@@ -95,11 +95,11 @@ describe UserCredentialsController do |
||
95 | 95 |
describe "DELETE destroy" do |
96 | 96 |
it "destroys only UserCredentials owned by the current user" do |
97 | 97 |
expect { |
98 |
- delete :destroy, :id => user_credentials(:bob_aws_key).to_param |
|
98 |
+ delete :destroy, params: {:id => user_credentials(:bob_aws_key).to_param} |
|
99 | 99 |
}.to change(UserCredential, :count).by(-1) |
100 | 100 |
|
101 | 101 |
expect { |
102 |
- delete :destroy, :id => user_credentials(:jane_aws_key).to_param |
|
102 |
+ delete :destroy, params: {:id => user_credentials(:jane_aws_key).to_param} |
|
103 | 103 |
}.to raise_error(ActiveRecord::RecordNotFound) |
104 | 104 |
end |
105 | 105 |
end |
@@ -2,16 +2,19 @@ require 'rails_helper' |
||
2 | 2 |
|
3 | 3 |
module Users |
4 | 4 |
describe RegistrationsController do |
5 |
- include Devise::TestHelpers |
|
6 |
- |
|
7 | 5 |
describe "POST create" do |
6 |
+ before do |
|
7 |
+ @request.env["devise.mapping"] = Devise.mappings[:user] |
|
8 |
+ end |
|
9 |
+ |
|
8 | 10 |
context 'with valid params' do |
9 | 11 |
it "imports the default scenario for the new user" do |
10 | 12 |
mock(DefaultScenarioImporter).import(is_a(User)) |
11 | 13 |
|
12 |
- @request.env["devise.mapping"] = Devise.mappings[:user] |
|
13 |
- post :create, :user => {username: 'jdoe', email: 'jdoe@example.com', |
|
14 |
- password: 's3cr3t55', password_confirmation: 's3cr3t55', admin: false, invitation_code: 'try-huginn'} |
|
14 |
+ post :create, params: { |
|
15 |
+ :user => {username: 'jdoe', email: 'jdoe@example.com', |
|
16 |
+ password: 's3cr3t55', password_confirmation: 's3cr3t55', invitation_code: 'try-huginn'} |
|
17 |
+ } |
|
15 | 18 |
end |
16 | 19 |
end |
17 | 20 |
|
@@ -19,9 +22,12 @@ module Users |
||
19 | 22 |
it "does not import the default scenario" do |
20 | 23 |
stub(DefaultScenarioImporter).import(is_a(User)) { fail "Should not attempt import" } |
21 | 24 |
|
22 |
- @request.env["devise.mapping"] = Devise.mappings[:user] |
|
23 | 25 |
setup_controller_for_warden |
24 |
- post :create, :user => {} |
|
26 |
+ post :create, params: {:user => {}} |
|
27 |
+ end |
|
28 |
+ |
|
29 |
+ it 'does not allow to set the admin flag' do |
|
30 |
+ expect { post :create, params: {:user => {admin: 'true'}} }.to raise_error(ActionController::UnpermittedParameters) |
|
25 | 31 |
end |
26 | 32 |
end |
27 | 33 |
end |
@@ -26,14 +26,14 @@ describe WebRequestsController do |
||
26 | 26 |
|
27 | 27 |
it "should not require login to receive a web request" do |
28 | 28 |
expect(@agent.last_web_request_at).to be_nil |
29 |
- post :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5" |
|
29 |
+ post :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"} |
|
30 | 30 |
expect(@agent.reload.last_web_request_at).to be_within(2).of(Time.now) |
31 | 31 |
expect(response.body).to eq("success") |
32 | 32 |
expect(response).to be_success |
33 | 33 |
end |
34 | 34 |
|
35 | 35 |
it "should call receive_web_request" do |
36 |
- post :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5" |
|
36 |
+ post :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"} |
|
37 | 37 |
@agent.reload |
38 | 38 |
expect(@agent.memory[:web_request_values]).to eq({ 'key' => "value", 'another_key' => "5" }) |
39 | 39 |
expect(@agent.memory[:web_request_format]).to eq("text/html") |
@@ -42,14 +42,14 @@ describe WebRequestsController do |
||
42 | 42 |
expect(response.headers['Content-Type']).to eq('text/plain; charset=utf-8') |
43 | 43 |
expect(response).to be_success |
44 | 44 |
|
45 |
- post :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "not_my_secret", :no => "go" |
|
45 |
+ post :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "not_my_secret", :no => "go"} |
|
46 | 46 |
expect(@agent.reload.memory[:web_request_values]).not_to eq({ 'no' => "go" }) |
47 | 47 |
expect(response.body).to eq("failure") |
48 | 48 |
expect(response).to be_missing |
49 | 49 |
end |
50 | 50 |
|
51 | 51 |
it "should accept gets" do |
52 |
- get :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5" |
|
52 |
+ get :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"} |
|
53 | 53 |
@agent.reload |
54 | 54 |
expect(@agent.memory[:web_request_values]).to eq({ 'key' => "value", 'another_key' => "5" }) |
55 | 55 |
expect(@agent.memory[:web_request_format]).to eq("text/html") |
@@ -59,19 +59,19 @@ describe WebRequestsController do |
||
59 | 59 |
end |
60 | 60 |
|
61 | 61 |
it "should pass through the received format" do |
62 |
- get :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5", :format => :json |
|
62 |
+ get :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"}, :format => :json |
|
63 | 63 |
@agent.reload |
64 | 64 |
expect(@agent.memory[:web_request_values]).to eq({ 'key' => "value", 'another_key' => "5" }) |
65 | 65 |
expect(@agent.memory[:web_request_format]).to eq("application/json") |
66 | 66 |
expect(@agent.memory[:web_request_method]).to eq("get") |
67 | 67 |
|
68 |
- post :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5", :format => :xml |
|
68 |
+ post :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"}, :format => :xml |
|
69 | 69 |
@agent.reload |
70 | 70 |
expect(@agent.memory[:web_request_values]).to eq({ 'key' => "value", 'another_key' => "5" }) |
71 | 71 |
expect(@agent.memory[:web_request_format]).to eq("application/xml") |
72 | 72 |
expect(@agent.memory[:web_request_method]).to eq("post") |
73 | 73 |
|
74 |
- put :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5", :format => :atom |
|
74 |
+ put :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"}, :format => :atom |
|
75 | 75 |
@agent.reload |
76 | 76 |
expect(@agent.memory[:web_request_values]).to eq({ 'key' => "value", 'another_key' => "5" }) |
77 | 77 |
expect(@agent.memory[:web_request_format]).to eq("application/atom+xml") |
@@ -81,17 +81,17 @@ describe WebRequestsController do |
||
81 | 81 |
it "can accept a content-type to return" do |
82 | 82 |
@agent.memory['content_type'] = 'application/json' |
83 | 83 |
@agent.save! |
84 |
- get :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5" |
|
84 |
+ get :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"} |
|
85 | 85 |
expect(response.headers['Content-Type']).to eq('application/json; charset=utf-8') |
86 | 86 |
end |
87 | 87 |
|
88 | 88 |
it "should fail on incorrect users" do |
89 |
- post :handle_request, :user_id => users(:jane).to_param, :agent_id => @agent.id, :secret => "my_secret", :no => "go" |
|
89 |
+ post :handle_request, params: {:user_id => users(:jane).to_param, :agent_id => @agent.id, :secret => "my_secret", :no => "go"} |
|
90 | 90 |
expect(response).to be_missing |
91 | 91 |
end |
92 | 92 |
|
93 | 93 |
it "should fail on incorrect agents" do |
94 |
- post :handle_request, :user_id => users(:bob).to_param, :agent_id => 454545, :secret => "my_secret", :no => "go" |
|
94 |
+ post :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => 454545, :secret => "my_secret", :no => "go"} |
|
95 | 95 |
expect(response).to be_missing |
96 | 96 |
end |
97 | 97 |
|
@@ -102,7 +102,7 @@ describe WebRequestsController do |
||
102 | 102 |
end |
103 | 103 |
|
104 | 104 |
it "should create events without requiring login" do |
105 |
- post :update_location, user_id: users(:bob).to_param, secret: "my_secret", longitude: 123, latitude: 45, something: "else" |
|
105 |
+ post :update_location, params: {user_id: users(:bob).to_param, secret: "my_secret", longitude: 123, latitude: 45, something: "else"} |
|
106 | 106 |
expect(@agent.events.last.payload).to eq({ 'longitude' => "123", 'latitude' => "45", 'something' => "else" }) |
107 | 107 |
expect(@agent.events.last.lat).to eq(45) |
108 | 108 |
expect(@agent.events.last.lng).to eq(123) |
@@ -112,13 +112,13 @@ describe WebRequestsController do |
||
112 | 112 |
@jane_agent = Agent.build_for_type("Agents::UserLocationAgent", users(:jane), name: "something", options: { secret: "my_secret" }) |
113 | 113 |
@jane_agent.save! |
114 | 114 |
|
115 |
- post :update_location, user_id: users(:bob).to_param, secret: "my_secret", longitude: 123, latitude: 45, something: "else" |
|
115 |
+ post :update_location, params: {user_id: users(:bob).to_param, secret: "my_secret", longitude: 123, latitude: 45, something: "else"} |
|
116 | 116 |
expect(@agent.events.last.payload).to eq({ 'longitude' => "123", 'latitude' => "45", 'something' => "else" }) |
117 | 117 |
expect(@jane_agent.events).to be_empty |
118 | 118 |
end |
119 | 119 |
|
120 | 120 |
it "should raise a 404 error when given an invalid user id" do |
121 |
- post :update_location, user_id: "123", secret: "not_my_secret", longitude: 123, latitude: 45, something: "else" |
|
121 |
+ post :update_location, params: {user_id: "123", secret: "not_my_secret", longitude: 123, latitude: 45, something: "else"} |
|
122 | 122 |
expect(response).to be_missing |
123 | 123 |
end |
124 | 124 |
|
@@ -127,7 +127,7 @@ describe WebRequestsController do |
||
127 | 127 |
@agent2.save! |
128 | 128 |
|
129 | 129 |
expect { |
130 |
- post :update_location, user_id: users(:bob).to_param, secret: "my_secret2", longitude: 123, latitude: 45, something: "else" |
|
130 |
+ post :update_location, params: {user_id: users(:bob).to_param, secret: "my_secret2", longitude: 123, latitude: 45, something: "else"} |
|
131 | 131 |
expect(@agent2.events.last.payload).to eq({ 'longitude' => "123", 'latitude' => "45", 'something' => "else" }) |
132 | 132 |
}.not_to change { @agent.events.count } |
133 | 133 |
end |
@@ -44,6 +44,7 @@ |
||
44 | 44 |
<category>calendar</category> |
45 | 45 |
<category>menubar</category> |
46 | 46 |
<category>osx</category> |
47 |
+ <enclosure url="http://c.1tw.org/images/2015/itsy.png" length="48249" type="image/png" /> |
|
47 | 48 |
</item> |
48 | 49 |
<item> |
49 | 50 |
<title>Magic Wormhole</title> |
@@ -208,8 +209,7 @@ |
||
208 | 209 |
</item> |
209 | 210 |
<item> |
210 | 211 |
<title>Showgoers</title> |
211 |
- <description><a href="http://showgoers.tv/">Showgoers</a>: <blockquote> <p>Showgoers is a Chrome browser extension to synchronize your Netflix player with someone else so that you can co-watch the same movie on different computers with no hassle. Syncing up your player is as easy as sharing a URL.</p> </blockquote> |
|
212 |
- </description> |
|
212 |
+ <description><a href="http://showgoers.tv/" onmouseover="javascript:void(0)">Showgoers</a>: <blockquote> <p>Showgoers is a Chrome browser extension to synchronize your Netflix player with someone else so that you can co-watch the same movie on different computers with no hassle. Syncing up your player is as easy as sharing a URL.</p> </blockquote><script>some code</script></description> |
|
213 | 213 |
<link>http://onethingwell.org/post/125509667816</link> |
214 | 214 |
<guid>http://onethingwell.org/post/125509667816</guid> |
215 | 215 |
<pubDate>Fri, 31 Jul 2015 13:00:13 +0100</pubDate> |
@@ -12,6 +12,7 @@ |
||
12 | 12 |
<li><a href="https://www.google.ca/search?q=위키백과:대문">unicode param</a></li> |
13 | 13 |
<li><a href="http://ko.wikipedia.org/wiki/%EC%9C%84%ED%82%A4%EB%B0%B1%EA%B3%BC:%EB%8C%80%EB%AC%B8">percent encoded url</a></li> |
14 | 14 |
<li><a href="https://www.google.ca/search?q=%EC%9C%84%ED%82%A4%EB%B0%B1%EA%B3%BC:%EB%8C%80%EB%AC%B8">percent encoded param</a></li> |
15 |
+ <li><a href="http://[::1]/path[]?query[]=foo">brackets</a></li> |
|
15 | 16 |
</ul> |
16 | 17 |
</body> |
17 |
-</html> |
|
18 |
+</html> |
@@ -0,0 +1,76 @@ |
||
1 |
+require 'rails_helper' |
|
2 |
+ |
|
3 |
+describe "Dry running an Agent", js: true do |
|
4 |
+ let(:agent) { agents(:bob_website_agent) } |
|
5 |
+ let(:formatting_agent) { agents(:bob_formatting_agent) } |
|
6 |
+ let(:user) { users(:bob) } |
|
7 |
+ let(:emitter) { agents(:bob_weather_agent) } |
|
8 |
+ |
|
9 |
+ before(:each) do |
|
10 |
+ login_as(user) |
|
11 |
+ end |
|
12 |
+ |
|
13 |
+ def open_dry_run_modal(agent) |
|
14 |
+ visit edit_agent_path(agent) |
|
15 |
+ click_on("Dry Run") |
|
16 |
+ expect(page).to have_text('Event to send') |
|
17 |
+ end |
|
18 |
+ |
|
19 |
+ context 'successful dry runs' do |
|
20 |
+ before do |
|
21 |
+ stub_request(:get, "http://xkcd.com/"). |
|
22 |
+ with(:headers => {'Accept-Encoding'=>'gzip,deflate', 'User-Agent'=>'Huginn - https://github.com/cantino/huginn'}). |
|
23 |
+ to_return(:status => 200, :body => File.read(Rails.root.join("spec/data_fixtures/xkcd.html")), :headers => {}) |
|
24 |
+ end |
|
25 |
+ |
|
26 |
+ it 'shows the dry run pop up without previous events and selects the events tab when a event was created' do |
|
27 |
+ open_dry_run_modal(agent) |
|
28 |
+ click_on("Dry Run") |
|
29 |
+ expect(page).to have_text('Biologists play reverse') |
|
30 |
+ expect(page).to have_selector(:css, 'li[role="presentation"].active a[href="#tabEvents"]') |
|
31 |
+ end |
|
32 |
+ |
|
33 |
+ it 'shows the dry run pop up with previous events and allows use previously received event' do |
|
34 |
+ emitter.events << Event.new(payload: {url: "http://xkcd.com/"}) |
|
35 |
+ agent.sources << emitter |
|
36 |
+ agent.options.merge!('url' => '', 'url_from_event' => '{{url}}') |
|
37 |
+ agent.save! |
|
38 |
+ |
|
39 |
+ open_dry_run_modal(agent) |
|
40 |
+ find('.dry-run-event-sample').click |
|
41 |
+ within(:css, '.modal .builder') do |
|
42 |
+ expect(page).to have_text('http://xkcd.com/') |
|
43 |
+ end |
|
44 |
+ click_on("Dry Run") |
|
45 |
+ expect(page).to have_text('Biologists play reverse') |
|
46 |
+ expect(page).to have_selector(:css, 'li[role="presentation"].active a[href="#tabEvents"]') |
|
47 |
+ end |
|
48 |
+ |
|
49 |
+ it 'sends escape characters correctly to the backend' do |
|
50 |
+ emitter.events << Event.new(payload: {data: "Line 1\nLine 2\nLine 3"}) |
|
51 |
+ formatting_agent.sources << emitter |
|
52 |
+ formatting_agent.options.merge!('instructions' => {'data' => "{{data | newline_to_br | strip_newlines | split: '<br />' | join: ','}}"}) |
|
53 |
+ formatting_agent.save! |
|
54 |
+ |
|
55 |
+ open_dry_run_modal(formatting_agent) |
|
56 |
+ find('.dry-run-event-sample').click |
|
57 |
+ within(:css, '.modal .builder') do |
|
58 |
+ expect(page).to have_text('Line 1\nLine 2\nLine 3') |
|
59 |
+ end |
|
60 |
+ click_on("Dry Run") |
|
61 |
+ expect(page).to have_text('Line 1,Line 2,Line 3') |
|
62 |
+ expect(page).to have_selector(:css, 'li[role="presentation"].active a[href="#tabEvents"]') |
|
63 |
+ end |
|
64 |
+ end |
|
65 |
+ |
|
66 |
+ it 'shows the dry run pop up without previous events and selects the log tab when no event was created' do |
|
67 |
+ stub_request(:get, "http://xkcd.com/"). |
|
68 |
+ with(:headers => {'Accept-Encoding'=>'gzip,deflate', 'User-Agent'=>'Huginn - https://github.com/cantino/huginn'}). |
|
69 |
+ to_return(:status => 200, :body => "", :headers => {}) |
|
70 |
+ |
|
71 |
+ open_dry_run_modal(agent) |
|
72 |
+ click_on("Dry Run") |
|
73 |
+ expect(page).to have_text('Dry Run started') |
|
74 |
+ expect(page).to have_selector(:css, 'li[role="presentation"].active a[href="#tabLog"]') |
|
75 |
+ end |
|
76 |
+end |
@@ -0,0 +1,10 @@ |
||
1 |
+require 'capybara_helper' |
|
2 |
+ |
|
3 |
+describe "form configuring agents", js: true do |
|
4 |
+ it 'completes fields with predefined array values' do |
|
5 |
+ login_as(users(:bob)) |
|
6 |
+ visit edit_agent_path(agents(:bob_csv_agent)) |
|
7 |
+ check('Propagate immediately') |
|
8 |
+ select2("serialize", from: "Mode") |
|
9 |
+ end |
|
10 |
+end |
@@ -0,0 +1,46 @@ |
||
1 |
+require 'rails_helper' |
|
2 |
+ |
|
3 |
+describe ScenarioImportsController do |
|
4 |
+ let(:user) { users(:bob) } |
|
5 |
+ |
|
6 |
+ before do |
|
7 |
+ login_as(user) |
|
8 |
+ end |
|
9 |
+ |
|
10 |
+ it 'renders the import form' do |
|
11 |
+ visit new_scenario_imports_path |
|
12 |
+ expect(page).to have_text('Import a Public Scenario') |
|
13 |
+ end |
|
14 |
+ |
|
15 |
+ it 'requires a URL or file uplaod' do |
|
16 |
+ visit new_scenario_imports_path |
|
17 |
+ click_on 'Start Import' |
|
18 |
+ expect(page).to have_text('Please provide either a Scenario JSON File or a Public Scenario URL.') |
|
19 |
+ end |
|
20 |
+ |
|
21 |
+ it 'imports a scenario that does not exist yet' do |
|
22 |
+ visit new_scenario_imports_path |
|
23 |
+ attach_file('Option 2: Upload a Scenario JSON File', File.join(Rails.root, 'data/default_scenario.json')) |
|
24 |
+ click_on 'Start Import' |
|
25 |
+ expect(page).to have_text('This scenario has a few agents to get you started. Feel free to change them or delete them as you see fit!') |
|
26 |
+ expect(page).not_to have_text('This Scenario already exists in your system.') |
|
27 |
+ check('I confirm that I want to import these Agents.') |
|
28 |
+ click_on 'Finish Import' |
|
29 |
+ expect(page).to have_text('Import successful!') |
|
30 |
+ end |
|
31 |
+ |
|
32 |
+ it 'asks to accept conflicts when the scenario was modified' do |
|
33 |
+ DefaultScenarioImporter.seed(user) |
|
34 |
+ agent = user.agents.where(name: 'Rain Notifier').first |
|
35 |
+ agent.options['expected_receive_period_in_days'] = 9001 |
|
36 |
+ agent.save! |
|
37 |
+ visit new_scenario_imports_path |
|
38 |
+ attach_file('Option 2: Upload a Scenario JSON File', File.join(Rails.root, 'data/default_scenario.json')) |
|
39 |
+ click_on 'Start Import' |
|
40 |
+ expect(page).to have_text('This Scenario already exists in your system.') |
|
41 |
+ expect(page).to have_text('9001') |
|
42 |
+ check('I confirm that I want to import these Agents.') |
|
43 |
+ click_on 'Finish Import' |
|
44 |
+ expect(page).to have_text('Import successful!') |
|
45 |
+ end |
|
46 |
+end |
@@ -0,0 +1,21 @@ |
||
1 |
+require 'capybara_helper' |
|
2 |
+ |
|
3 |
+describe "handling undefined agents" do |
|
4 |
+ before do |
|
5 |
+ login_as(users(:bob)) |
|
6 |
+ agent = agents(:bob_website_agent) |
|
7 |
+ agent.update_attribute(:type, 'Agents::UndefinedAgent') |
|
8 |
+ end |
|
9 |
+ |
|
10 |
+ it 'renders the error page' do |
|
11 |
+ visit agents_path |
|
12 |
+ expect(page).to have_text("Error: Agent(s) are 'missing in action'") |
|
13 |
+ expect(page).to have_text('Undefined Agent') |
|
14 |
+ end |
|
15 |
+ |
|
16 |
+ it 'deletes all undefined agents' do |
|
17 |
+ visit agents_path |
|
18 |
+ click_on('Delete Missing Agents') |
|
19 |
+ expect(page).to have_text('Your Agents') |
|
20 |
+ end |
|
21 |
+end |
@@ -60,6 +60,14 @@ bob_weather_agent: |
||
60 | 60 |
keep_events_for: <%= 45.days %> |
61 | 61 |
options: <%= { :location => 94102, :lat => 37.779329, :lng => -122.41915, :api_key => 'test' }.to_json.inspect %> |
62 | 62 |
|
63 |
+bob_formatting_agent: |
|
64 |
+ type: Agents::EventFormattingAgent |
|
65 |
+ user: bob |
|
66 |
+ name: "Formatting Agent" |
|
67 |
+ guid: <%= SecureRandom.hex %> |
|
68 |
+ keep_events_for: <%= 45.days %> |
|
69 |
+ options: <%= { instructions: {}, mode: 'clean' }.to_json.inspect %> |
|
70 |
+ |
|
63 | 71 |
jane_weather_agent: |
64 | 72 |
type: Agents::WeatherAgent |
65 | 73 |
user: jane |
@@ -136,14 +144,28 @@ bob_manual_event_agent: |
||
136 | 144 |
bob_basecamp_agent: |
137 | 145 |
type: Agents::BasecampAgent |
138 | 146 |
user: bob |
147 |
+ name: "bob basecamp agent" |
|
139 | 148 |
service: generic |
140 | 149 |
guid: <%= SecureRandom.hex %> |
150 |
+ options: <%= { |
|
151 |
+ :project_id => "12345", |
|
152 |
+ }.to_json.inspect %> |
|
153 |
+ |
|
154 |
+bob_csv_agent: |
|
155 |
+ type: Agents::CsvAgent |
|
156 |
+ user: bob |
|
157 |
+ name: "Bob's CsvAgent" |
|
158 |
+ guid: <%= SecureRandom.hex %> |
|
141 | 159 |
|
142 | 160 |
jane_basecamp_agent: |
143 | 161 |
type: Agents::BasecampAgent |
144 | 162 |
user: jane |
163 |
+ name: "jane basecamp agent" |
|
145 | 164 |
service: generic |
146 | 165 |
guid: <%= SecureRandom.hex %> |
166 |
+ options: <%= { |
|
167 |
+ :project_id => "12345", |
|
168 |
+ }.to_json.inspect %> |
|
147 | 169 |
|
148 | 170 |
|
149 | 171 |
bob_data_output_agent: |
@@ -44,17 +44,17 @@ describe Agents::BoxcarAgent do |
||
44 | 44 |
|
45 | 45 |
it "should raise error when invalid response arrives" do |
46 | 46 |
stub(HTTParty).post { {"blah" => "blah"} } |
47 |
- expect{@checker.send_notification}.to raise_error |
|
47 |
+ expect { @checker.send_notification({}) }.to raise_error(StandardError, /Invalid response from Boxcar:/) |
|
48 | 48 |
end |
49 | 49 |
|
50 | 50 |
it "should raise error when response says unauthorized" do |
51 |
- stub(HTTParty).post '{"Response":"Not authorized"}' |
|
52 |
- expect{@checker.send_notification}.to raise_error |
|
51 |
+ stub(HTTParty).post { {"Response" => "Not authorized"} } |
|
52 |
+ expect { @checker.send_notification({}) }.to raise_error(StandardError, /Not authorized/) |
|
53 | 53 |
end |
54 | 54 |
|
55 | 55 |
it "should raise error when response has an error" do |
56 |
- stub(HTTParty).post '{"error": {"message": "Sample error"}}' |
|
57 |
- expect{@checker.send_notification}.to raise_error |
|
56 |
+ stub(HTTParty).post { {"error" => {"message" => "Sample error"}} } |
|
57 |
+ expect { @checker.send_notification({}) }.to raise_error(StandardError, /Sample error/) |
|
58 | 58 |
end |
59 | 59 |
end |
60 | 60 |
end |
@@ -142,7 +142,7 @@ describe Agents::DataOutputAgent do |
||
142 | 142 |
"url" => "http://imgs.xkcd.com/comics/evolving0.png", |
143 | 143 |
"title" => "Evolving yet again with a past date", |
144 | 144 |
"date" => '2014/05/05', |
145 |
- "hovertext" => "Something else" |
|
145 |
+ "hovertext" => "A small text" |
|
146 | 146 |
} |
147 | 147 |
end |
148 | 148 |
|
@@ -166,7 +166,7 @@ describe Agents::DataOutputAgent do |
||
166 | 166 |
|
167 | 167 |
<item> |
168 | 168 |
<title>Evolving yet again with a past date</title> |
169 |
- <description>Secret hovertext: Something else</description> |
|
169 |
+ <description>Secret hovertext: A small text</description> |
|
170 | 170 |
<link>http://imgs.xkcd.com/comics/evolving0.png</link> |
171 | 171 |
<pubDate>#{Time.zone.parse(event3.payload['date']).rfc2822}</pubDate> |
172 | 172 |
<guid isPermaLink="false">#{event3.id}</guid> |
@@ -216,7 +216,7 @@ describe Agents::DataOutputAgent do |
||
216 | 216 |
'items' => [ |
217 | 217 |
{ |
218 | 218 |
'title' => 'Evolving yet again with a past date', |
219 |
- 'description' => 'Secret hovertext: Something else', |
|
219 |
+ 'description' => 'Secret hovertext: A small text', |
|
220 | 220 |
'link' => 'http://imgs.xkcd.com/comics/evolving0.png', |
221 | 221 |
'guid' => {"contents" => event3.id, "isPermaLink" => "false"}, |
222 | 222 |
'pubDate' => Time.zone.parse(event3.payload['date']).rfc2822, |
@@ -242,16 +242,62 @@ describe Agents::DataOutputAgent do |
||
242 | 242 |
}) |
243 | 243 |
end |
244 | 244 |
|
245 |
+ context 'with more events' do |
|
246 |
+ let!(:event4) do |
|
247 |
+ agents(:bob_website_agent).create_event payload: { |
|
248 |
+ 'site_title' => 'XKCD', |
|
249 |
+ 'url' => 'http://imgs.xkcd.com/comics/comic1.png', |
|
250 |
+ 'title' => 'Comic 1', |
|
251 |
+ 'date' => '', |
|
252 |
+ 'hovertext' => 'Hovertext for Comic 1' |
|
253 |
+ } |
|
254 |
+ end |
|
255 |
+ |
|
256 |
+ let!(:event5) do |
|
257 |
+ agents(:bob_website_agent).create_event payload: { |
|
258 |
+ 'site_title' => 'XKCD', |
|
259 |
+ 'url' => 'http://imgs.xkcd.com/comics/comic2.png', |
|
260 |
+ 'title' => 'Comic 2', |
|
261 |
+ 'date' => '', |
|
262 |
+ 'hovertext' => 'Hovertext for Comic 2' |
|
263 |
+ } |
|
264 |
+ end |
|
265 |
+ |
|
266 |
+ let!(:event6) do |
|
267 |
+ agents(:bob_website_agent).create_event payload: { |
|
268 |
+ 'site_title' => 'XKCD', |
|
269 |
+ 'url' => 'http://imgs.xkcd.com/comics/comic3.png', |
|
270 |
+ 'title' => 'Comic 3', |
|
271 |
+ 'date' => '', |
|
272 |
+ 'hovertext' => 'Hovertext for Comic 3' |
|
273 |
+ } |
|
274 |
+ end |
|
275 |
+ |
|
276 |
+ describe 'limiting' do |
|
277 |
+ it 'can select the last `events_to_show` events' do |
|
278 |
+ agent.options['events_to_show'] = 2 |
|
279 |
+ content, _status, _content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json') |
|
280 |
+ expect(content['items'].map {|i| i["title"] }).to eq(["Comic 3", "Comic 2"]) |
|
281 |
+ end |
|
282 |
+ end |
|
283 |
+ end |
|
284 |
+ |
|
245 | 285 |
describe 'ordering' do |
246 | 286 |
before do |
247 |
- agent.options['events_order'] = ['{{title}}'] |
|
287 |
+ agent.options['events_order'] = ['{{hovertext}}'] |
|
288 |
+ agent.options['events_list_order'] = ['{{title}}'] |
|
248 | 289 |
end |
249 | 290 |
|
250 |
- it 'can reorder the events_to_show last events based on a Liquid expression' do |
|
291 |
+ it 'can reorder the last `events_to_show` events based on a Liquid expression' do |
|
292 |
+ agent.options['events_to_show'] = 2 |
|
293 |
+ asc_content, _status, _content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json') |
|
294 |
+ expect(asc_content['items'].map {|i| i["title"] }).to eq(["Evolving", "Evolving again"]) |
|
295 |
+ |
|
296 |
+ agent.options['events_to_show'] = 40 |
|
251 | 297 |
asc_content, _status, _content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json') |
252 | 298 |
expect(asc_content['items'].map {|i| i["title"] }).to eq(["Evolving", "Evolving again", "Evolving yet again with a past date"]) |
253 | 299 |
|
254 |
- agent.options['events_order'] = [['{{title}}', 'string', true]] |
|
300 |
+ agent.options['events_list_order'] = [['{{title}}', 'string', true]] |
|
255 | 301 |
|
256 | 302 |
desc_content, _status, _content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json') |
257 | 303 |
expect(desc_content['items']).to eq(asc_content['items'].reverse) |
@@ -8,11 +8,11 @@ describe Agents::EmailDigestAgent do |
||
8 | 8 |
end |
9 | 9 |
|
10 | 10 |
before do |
11 |
- @checker = Agents::EmailDigestAgent.new(:name => "something", :options => { :expected_receive_period_in_days => "2", :subject => "something interesting" }) |
|
11 |
+ @checker = Agents::EmailDigestAgent.new(:name => "something", :options => {:expected_receive_period_in_days => "2", :subject => "something interesting"}) |
|
12 | 12 |
@checker.user = users(:bob) |
13 | 13 |
@checker.save! |
14 | 14 |
|
15 |
- @checker1 = Agents::EmailDigestAgent.new(:name => "something", :options => { :expected_receive_period_in_days => "2", :subject => "something interesting", :content_type => "text/plain" }) |
|
15 |
+ @checker1 = Agents::EmailDigestAgent.new(:name => "something", :options => {:expected_receive_period_in_days => "2", :subject => "something interesting", :content_type => "text/plain"}) |
|
16 | 16 |
@checker1.user = users(:bob) |
17 | 17 |
@checker1.save! |
18 | 18 |
end |
@@ -25,16 +25,16 @@ describe Agents::EmailDigestAgent do |
||
25 | 25 |
it "queues any payloads it receives" do |
26 | 26 |
event1 = Event.new |
27 | 27 |
event1.agent = agents(:bob_rain_notifier_agent) |
28 |
- event1.payload = { :data => "Something you should know about" } |
|
28 |
+ event1.payload = {:data => "Something you should know about"} |
|
29 | 29 |
event1.save! |
30 | 30 |
|
31 | 31 |
event2 = Event.new |
32 | 32 |
event2.agent = agents(:bob_weather_agent) |
33 |
- event2.payload = { :data => "Something else you should know about" } |
|
33 |
+ event2.payload = {:data => "Something else you should know about"} |
|
34 | 34 |
event2.save! |
35 | 35 |
|
36 | 36 |
Agents::EmailDigestAgent.async_receive(@checker.id, [event1.id, event2.id]) |
37 |
- expect(@checker.reload.memory[:queue]).to eq([{ 'data' => "Something you should know about" }, { 'data' => "Something else you should know about" }]) |
|
37 |
+ expect(@checker.reload.memory['events']).to match([event1.id, event2.id]) |
|
38 | 38 |
end |
39 | 39 |
end |
40 | 40 |
|
@@ -44,25 +44,34 @@ describe Agents::EmailDigestAgent do |
||
44 | 44 |
Agents::EmailDigestAgent.async_check(@checker.id) |
45 | 45 |
expect(ActionMailer::Base.deliveries).to eq([]) |
46 | 46 |
|
47 |
- @checker.memory[:queue] = [{ :data => "Something you should know about" }, |
|
48 |
- { :title => "Foo", :url => "http://google.com", :bar => 2 }, |
|
49 |
- { "message" => "hi", :woah => "there" }, |
|
50 |
- { "test" => 2 }] |
|
51 |
- @checker.memory[:events] = [1,2,3,4] |
|
52 |
- @checker.save! |
|
53 |
- |
|
54 |
- Agents::EmailDigestAgent.async_check(@checker.id) |
|
47 |
+ payloads = [ |
|
48 |
+ {:data => "Something you should know about"}, |
|
49 |
+ {:title => "Foo", :url => "http://google.com", :bar => 2}, |
|
50 |
+ {"message" => "hi", :woah => "there"}, |
|
51 |
+ {"test" => 2} |
|
52 |
+ ] |
|
53 |
+ |
|
54 |
+ events = payloads.map do |payload| |
|
55 |
+ Event.new.tap do |event| |
|
56 |
+ event.agent = agents(:bob_weather_agent) |
|
57 |
+ event.payload = payload |
|
58 |
+ event.save! |
|
59 |
+ end |
|
60 |
+ end |
|
61 |
+ |
|
62 |
+ Agents::DigestAgent.async_receive(@checker.id, events.map(&:id)) |
|
63 |
+ @checker.sources << agents(:bob_weather_agent) |
|
64 |
+ Agents::DigestAgent.async_check(@checker.id) |
|
55 | 65 |
|
56 | 66 |
expect(ActionMailer::Base.deliveries.last.to).to eq(["bob@example.com"]) |
57 | 67 |
expect(ActionMailer::Base.deliveries.last.subject).to eq("something interesting") |
58 | 68 |
expect(get_message_part(ActionMailer::Base.deliveries.last, /plain/).strip).to eq("Event\n data: Something you should know about\n\nFoo\n bar: 2\n url: http://google.com\n\nhi\n woah: there\n\nEvent\n test: 2") |
59 |
- expect(@checker.reload.memory[:queue]).to be_empty |
|
69 |
+ expect(@checker.reload.memory[:events]).to be_empty |
|
60 | 70 |
end |
61 | 71 |
|
62 | 72 |
it "logs and re-raises mailer errors" do |
63 | 73 |
mock(SystemMailer).send_message(anything) { raise Net::SMTPAuthenticationError.new("Wrong password") } |
64 | 74 |
|
65 |
- @checker.memory[:queue] = [{ :data => "Something you should know about" }] |
|
66 | 75 |
@checker.memory[:events] = [1] |
67 | 76 |
@checker.save! |
68 | 77 |
|
@@ -71,8 +80,6 @@ describe Agents::EmailDigestAgent do |
||
71 | 80 |
}.to raise_error(/Wrong password/) |
72 | 81 |
|
73 | 82 |
expect(@checker.reload.memory[:events]).not_to be_empty |
74 |
- expect(@checker.reload.memory[:queue]).not_to be_empty |
|
75 |
- |
|
76 | 83 |
expect(@checker.logs.last.message).to match(/Error sending digest mail .* Wrong password/) |
77 | 84 |
end |
78 | 85 |
|
@@ -84,7 +91,7 @@ describe Agents::EmailDigestAgent do |
||
84 | 91 |
Agent.async_check(agents(:bob_weather_agent).id) |
85 | 92 |
|
86 | 93 |
Agent.receive! |
87 |
- expect(@checker.reload.memory[:queue]).not_to be_empty |
|
94 |
+ expect(@checker.reload.memory[:events]).not_to be_empty |
|
88 | 95 |
|
89 | 96 |
Agents::EmailDigestAgent.async_check(@checker.id) |
90 | 97 |
|
@@ -94,18 +101,14 @@ describe Agents::EmailDigestAgent do |
||
94 | 101 |
expect(plain_email_text).to match(/avehumidity/) |
95 | 102 |
expect(html_email_text).to match(/avehumidity/) |
96 | 103 |
|
97 |
- expect(@checker.reload.memory[:queue]).to be_empty |
|
104 |
+ expect(@checker.reload.memory[:events]).to be_empty |
|
98 | 105 |
end |
99 |
- |
|
106 |
+ |
|
100 | 107 |
it "should send email with correct content type" do |
101 | 108 |
Agents::EmailDigestAgent.async_check(@checker1.id) |
102 | 109 |
expect(ActionMailer::Base.deliveries).to eq([]) |
103 | 110 |
|
104 |
- @checker1.memory[:queue] = [{ :data => "Something you should know about" }, |
|
105 |
- { :title => "Foo", :url => "http://google.com", :bar => 2 }, |
|
106 |
- { "message" => "hi", :woah => "there" }, |
|
107 |
- { "test" => 2 }] |
|
108 |
- @checker1.memory[:events] = [1,2,3,4] |
|
111 |
+ @checker1.memory[:events] = [1, 2, 3, 4] |
|
109 | 112 |
@checker1.save! |
110 | 113 |
|
111 | 114 |
Agents::EmailDigestAgent.async_check(@checker1.id) |
@@ -57,6 +57,11 @@ describe Agents::PostAgent do |
||
57 | 57 |
end |
58 | 58 |
|
59 | 59 |
it_behaves_like WebRequestConcern |
60 |
+ it_behaves_like 'FileHandlingConsumer' |
|
61 |
+ |
|
62 |
+ it 'renders the description markdown without errors' do |
|
63 |
+ expect { @checker.description }.not_to raise_error |
|
64 |
+ end |
|
60 | 65 |
|
61 | 66 |
describe "making requests" do |
62 | 67 |
it "can make requests of each type" do |
@@ -149,6 +154,19 @@ describe Agents::PostAgent do |
||
149 | 154 |
headers = @sent_requests[:post].first.headers |
150 | 155 |
expect(headers["Foo"]).to eq("a_variable") |
151 | 156 |
end |
157 |
+ |
|
158 |
+ it 'makes a multipart request when receiving a file_pointer' do |
|
159 |
+ WebMock.reset! |
|
160 |
+ stub_request(:post, "http://www.example.com/"). |
|
161 |
+ with(:body => "-------------RubyMultipartPost\r\nContent-Disposition: form-data; name=\"default\"\r\n\r\nvalue\r\n-------------RubyMultipartPost\r\nContent-Disposition: form-data; name=\"file\"; filename=\"local.path\"\r\nContent-Length: 8\r\nContent-Type: \r\nContent-Transfer-Encoding: binary\r\n\r\ntestdata\r\n-------------RubyMultipartPost--\r\n\r\n", |
|
162 |
+ :headers => {'Accept-Encoding'=>'gzip,deflate', 'Content-Length'=>'307', 'Content-Type'=>'multipart/form-data; boundary=-----------RubyMultipartPost', 'User-Agent'=>'Huginn - https://github.com/cantino/huginn'}). |
|
163 |
+ to_return(:status => 200, :body => "", :headers => {}) |
|
164 |
+ event = Event.new(payload: {file_pointer: {agent_id: 111, file: 'test'}}) |
|
165 |
+ io_mock = mock() |
|
166 |
+ mock(@checker).get_io(event) { StringIO.new("testdata") } |
|
167 |
+ @checker.options['no_merge'] = true |
|
168 |
+ @checker.receive([event]) |
|
169 |
+ end |
|
152 | 170 |
end |
153 | 171 |
|
154 | 172 |
describe "#check" do |
@@ -2,27 +2,29 @@ require 'rails_helper' |
||
2 | 2 |
|
3 | 3 |
describe Agents::PushoverAgent do |
4 | 4 |
before do |
5 |
- @checker = Agents::PushoverAgent.new(:name => 'Some Name', |
|
6 |
- :options => { :token => 'x', |
|
7 |
- :user => 'x', |
|
8 |
- :message => 'Some Message', |
|
9 |
- :device => 'Some Device', |
|
10 |
- :title => 'Some Message Title', |
|
11 |
- :url => 'http://someurl.com', |
|
12 |
- :url_title => 'Some Url Title', |
|
13 |
- :priority => 0, |
|
14 |
- :timestamp => 'false', |
|
15 |
- :sound => 'pushover', |
|
16 |
- :retry => 0, |
|
17 |
- :expire => 0, |
|
18 |
- :expected_receive_period_in_days => '1'}) |
|
19 |
- |
|
5 |
+ @checker = Agents::PushoverAgent.new(name: 'Some Name', |
|
6 |
+ options: { |
|
7 |
+ token: 'x', |
|
8 |
+ user: 'x', |
|
9 |
+ message: "{{ message | default: text | default: 'Some Message' }}", |
|
10 |
+ device: "{{ device | default: 'Some Device' }}", |
|
11 |
+ title: "{{ title | default: 'Some Message Title' }}", |
|
12 |
+ url: "{{ url | default: 'http://someurl.com' }}", |
|
13 |
+ url_title: "{{ url_title | default: 'Some Url Title' }}", |
|
14 |
+ priority: "{{ priority | default: 0 }}", |
|
15 |
+ timestamp: "{{ timestamp | default: 'false' }}", |
|
16 |
+ sound: "{{ sound | default: 'pushover' }}", |
|
17 |
+ retry: "{{ retry | default: 0 }}", |
|
18 |
+ expire: "{{ expire | default: 0 }}", |
|
19 |
+ expected_receive_period_in_days: '1' |
|
20 |
+ }) |
|
21 |
+ |
|
20 | 22 |
@checker.user = users(:bob) |
21 | 23 |
@checker.save! |
22 | 24 |
|
23 | 25 |
@event = Event.new |
24 | 26 |
@event.agent = agents(:bob_weather_agent) |
25 |
- @event.payload = { :message => 'Looks like its going to rain' } |
|
27 |
+ @event.payload = { message: 'Looks like its going to rain' } |
|
26 | 28 |
@event.save! |
27 | 29 |
|
28 | 30 |
@sent_notifications = [] |
@@ -33,12 +35,12 @@ describe Agents::PushoverAgent do |
||
33 | 35 |
it 'should make sure multiple events are being received' do |
34 | 36 |
event1 = Event.new |
35 | 37 |
event1.agent = agents(:bob_rain_notifier_agent) |
36 |
- event1.payload = { :message => 'Some message' } |
|
38 |
+ event1.payload = { message: 'Some message' } |
|
37 | 39 |
event1.save! |
38 | 40 |
|
39 | 41 |
event2 = Event.new |
40 | 42 |
event2.agent = agents(:bob_weather_agent) |
41 |
- event2.payload = { :message => 'Some other message' } |
|
43 |
+ event2.payload = { message: 'Some other message' } |
|
42 | 44 |
event2.save! |
43 | 45 |
|
44 | 46 |
@checker.receive([@event,event1,event2]) |
@@ -50,7 +52,7 @@ describe Agents::PushoverAgent do |
||
50 | 52 |
it 'should make sure event message overrides default message' do |
51 | 53 |
event = Event.new |
52 | 54 |
event.agent = agents(:bob_rain_notifier_agent) |
53 |
- event.payload = { :message => 'Some new message'} |
|
55 |
+ event.payload = { message: 'Some new message'} |
|
54 | 56 |
event.save! |
55 | 57 |
|
56 | 58 |
@checker.receive([event]) |
@@ -60,7 +62,7 @@ describe Agents::PushoverAgent do |
||
60 | 62 |
it 'should make sure event text overrides default message' do |
61 | 63 |
event = Event.new |
62 | 64 |
event.agent = agents(:bob_rain_notifier_agent) |
63 |
- event.payload = { :text => 'Some new text'} |
|
65 |
+ event.payload = { text: 'Some new text'} |
|
64 | 66 |
event.save! |
65 | 67 |
|
66 | 68 |
@checker.receive([event]) |
@@ -70,7 +72,7 @@ describe Agents::PushoverAgent do |
||
70 | 72 |
it 'should make sure event title overrides default title' do |
71 | 73 |
event = Event.new |
72 | 74 |
event.agent = agents(:bob_rain_notifier_agent) |
73 |
- event.payload = { :message => 'Some message', :title => 'Some new title' } |
|
75 |
+ event.payload = { message: 'Some message', title: 'Some new title' } |
|
74 | 76 |
event.save! |
75 | 77 |
|
76 | 78 |
@checker.receive([event]) |
@@ -80,7 +82,7 @@ describe Agents::PushoverAgent do |
||
80 | 82 |
it 'should make sure event url overrides default url' do |
81 | 83 |
event = Event.new |
82 | 84 |
event.agent = agents(:bob_rain_notifier_agent) |
83 |
- event.payload = { :message => 'Some message', :url => 'Some new url' } |
|
85 |
+ event.payload = { message: 'Some message', url: 'Some new url' } |
|
84 | 86 |
event.save! |
85 | 87 |
|
86 | 88 |
@checker.receive([event]) |
@@ -90,7 +92,7 @@ describe Agents::PushoverAgent do |
||
90 | 92 |
it 'should make sure event url_title overrides default url_title' do |
91 | 93 |
event = Event.new |
92 | 94 |
event.agent = agents(:bob_rain_notifier_agent) |
93 |
- event.payload = { :message => 'Some message', :url_title => 'Some new url_title' } |
|
95 |
+ event.payload = { message: 'Some message', url_title: 'Some new url_title' } |
|
94 | 96 |
event.save! |
95 | 97 |
|
96 | 98 |
@checker.receive([event]) |
@@ -100,17 +102,17 @@ describe Agents::PushoverAgent do |
||
100 | 102 |
it 'should make sure event priority overrides default priority' do |
101 | 103 |
event = Event.new |
102 | 104 |
event.agent = agents(:bob_rain_notifier_agent) |
103 |
- event.payload = { :message => 'Some message', :priority => 1 } |
|
105 |
+ event.payload = { message: 'Some message', priority: '1' } |
|
104 | 106 |
event.save! |
105 | 107 |
|
106 | 108 |
@checker.receive([event]) |
107 |
- expect(@sent_notifications[0]['priority']).to eq(1) |
|
109 |
+ expect(@sent_notifications[0]['priority']).to eq('1') |
|
108 | 110 |
end |
109 | 111 |
|
110 | 112 |
it 'should make sure event timestamp overrides default timestamp' do |
111 | 113 |
event = Event.new |
112 | 114 |
event.agent = agents(:bob_rain_notifier_agent) |
113 |
- event.payload = { :message => 'Some message', :timestamp => 'false' } |
|
115 |
+ event.payload = { message: 'Some message', timestamp: 'false' } |
|
114 | 116 |
event.save! |
115 | 117 |
|
116 | 118 |
@checker.receive([event]) |
@@ -120,7 +122,7 @@ describe Agents::PushoverAgent do |
||
120 | 122 |
it 'should make sure event sound overrides default sound' do |
121 | 123 |
event = Event.new |
122 | 124 |
event.agent = agents(:bob_rain_notifier_agent) |
123 |
- event.payload = { :message => 'Some message', :sound => 'Some new sound' } |
|
125 |
+ event.payload = { message: 'Some message', sound: 'Some new sound' } |
|
124 | 126 |
event.save! |
125 | 127 |
|
126 | 128 |
@checker.receive([event]) |
@@ -130,21 +132,21 @@ describe Agents::PushoverAgent do |
||
130 | 132 |
it 'should make sure event retry overrides default retry' do |
131 | 133 |
event = Event.new |
132 | 134 |
event.agent = agents(:bob_rain_notifier_agent) |
133 |
- event.payload = { :message => 'Some message', :retry => 1 } |
|
135 |
+ event.payload = { message: 'Some message', retry: 1 } |
|
134 | 136 |
event.save! |
135 | 137 |
|
136 | 138 |
@checker.receive([event]) |
137 |
- expect(@sent_notifications[0]['retry']).to eq(1) |
|
139 |
+ expect(@sent_notifications[0]['retry']).to eq('1') |
|
138 | 140 |
end |
139 | 141 |
|
140 | 142 |
it 'should make sure event expire overrides default expire' do |
141 | 143 |
event = Event.new |
142 | 144 |
event.agent = agents(:bob_rain_notifier_agent) |
143 |
- event.payload = { :message => 'Some message', :expire => 60 } |
|
145 |
+ event.payload = { message: 'Some message', expire: '60' } |
|
144 | 146 |
event.save! |
145 | 147 |
|
146 | 148 |
@checker.receive([event]) |
147 |
- expect(@sent_notifications[0]['expire']).to eq(60) |
|
149 |
+ expect(@sent_notifications[0]['expire']).to eq('60') |
|
148 | 150 |
end |
149 | 151 |
end |
150 | 152 |
|
@@ -158,7 +160,7 @@ describe Agents::PushoverAgent do |
||
158 | 160 |
expect(@checker.reload).to be_working |
159 | 161 |
two_days_from_now = 2.days.from_now |
160 | 162 |
stub(Time).now { two_days_from_now } |
161 |
- |
|
163 |
+ |
|
162 | 164 |
# More time has passed than the expected receive period without any new events |
163 | 165 |
expect(@checker.reload).not_to be_working |
164 | 166 |
end |
@@ -55,16 +55,57 @@ describe Agents::RssAgent do |
||
55 | 55 |
end |
56 | 56 |
|
57 | 57 |
describe "emitting RSS events" do |
58 |
- it "should emit items as events" do |
|
58 |
+ it "should emit items as events for an Atom feed" do |
|
59 |
+ agent.options['include_feed_info'] = true |
|
60 |
+ |
|
59 | 61 |
expect { |
60 | 62 |
agent.check |
61 | 63 |
}.to change { agent.events.count }.by(20) |
62 | 64 |
|
63 | 65 |
first, *, last = agent.events.last(20) |
66 |
+ [first, last].each do |event| |
|
67 |
+ expect(first.payload['feed']).to include({ |
|
68 |
+ "type" => "atom", |
|
69 |
+ "title" => "Recent Commits to huginn:master", |
|
70 |
+ "url" => "https://github.com/cantino/huginn/commits/master", |
|
71 |
+ "links" => [ |
|
72 |
+ { |
|
73 |
+ "type" => "text/html", |
|
74 |
+ "rel" => "alternate", |
|
75 |
+ "href" => "https://github.com/cantino/huginn/commits/master", |
|
76 |
+ }, |
|
77 |
+ { |
|
78 |
+ "type" => "application/atom+xml", |
|
79 |
+ "rel" => "self", |
|
80 |
+ "href" => "https://github.com/cantino/huginn/commits/master.atom", |
|
81 |
+ }, |
|
82 |
+ ], |
|
83 |
+ }) |
|
84 |
+ end |
|
64 | 85 |
expect(first.payload['url']).to eq("https://github.com/cantino/huginn/commit/d0a844662846cf3c83b94c637c1803f03db5a5b0") |
65 | 86 |
expect(first.payload['urls']).to eq(["https://github.com/cantino/huginn/commit/d0a844662846cf3c83b94c637c1803f03db5a5b0"]) |
87 |
+ expect(first.payload['links']).to eq([ |
|
88 |
+ { |
|
89 |
+ "href" => "https://github.com/cantino/huginn/commit/d0a844662846cf3c83b94c637c1803f03db5a5b0", |
|
90 |
+ "rel" => "alternate", |
|
91 |
+ "type" => "text/html", |
|
92 |
+ } |
|
93 |
+ ]) |
|
94 |
+ expect(first.payload['authors']).to eq(["cantino (https://github.com/cantino)"]) |
|
95 |
+ expect(first.payload['date_published']).to be_nil |
|
96 |
+ expect(first.payload['last_updated']).to eq("2014-07-16T22:26:22-07:00") |
|
66 | 97 |
expect(last.payload['url']).to eq("https://github.com/cantino/huginn/commit/d465158f77dcd9078697e6167b50abbfdfa8b1af") |
67 | 98 |
expect(last.payload['urls']).to eq(["https://github.com/cantino/huginn/commit/d465158f77dcd9078697e6167b50abbfdfa8b1af"]) |
99 |
+ expect(last.payload['links']).to eq([ |
|
100 |
+ { |
|
101 |
+ "href" => "https://github.com/cantino/huginn/commit/d465158f77dcd9078697e6167b50abbfdfa8b1af", |
|
102 |
+ "rel" => "alternate", |
|
103 |
+ "type" => "text/html", |
|
104 |
+ } |
|
105 |
+ ]) |
|
106 |
+ expect(last.payload['authors']).to eq(["CloCkWeRX (https://github.com/CloCkWeRX)"]) |
|
107 |
+ expect(last.payload['date_published']).to be_nil |
|
108 |
+ expect(last.payload['last_updated']).to eq("2014-07-01T16:37:47+09:30") |
|
68 | 109 |
end |
69 | 110 |
|
70 | 111 |
it "should emit items as events in the order specified in the events_order option" do |
@@ -82,6 +123,33 @@ describe Agents::RssAgent do |
||
82 | 123 |
expect(last.payload['urls']).to eq(["https://github.com/cantino/huginn/commit/0e80f5341587aace2c023b06eb9265b776ac4535"]) |
83 | 124 |
end |
84 | 125 |
|
126 |
+ it "should emit items as events for a FeedBurner RSS 2.0 feed" do |
|
127 |
+ agent.options['url'] = "http://feeds.feedburner.com/SlickdealsnetFP?format=atom" # This is actually RSS 2.0 w/ Atom extension |
|
128 |
+ agent.options['include_feed_info'] = true |
|
129 |
+ agent.save! |
|
130 |
+ |
|
131 |
+ expect { |
|
132 |
+ agent.check |
|
133 |
+ }.to change { agent.events.count }.by(79) |
|
134 |
+ |
|
135 |
+ first, *, last = agent.events.last(79) |
|
136 |
+ expect(first.payload['feed']).to include({ |
|
137 |
+ "type" => "rss", |
|
138 |
+ "title" => "SlickDeals.net", |
|
139 |
+ "description" => "Slick online shopping deals.", |
|
140 |
+ "url" => "http://slickdeals.net/", |
|
141 |
+ }) |
|
142 |
+ # Feedjira extracts feedburner:origLink |
|
143 |
+ expect(first.payload['url']).to eq("http://slickdeals.net/permadeal/130160/green-man-gaming---pc-games-tomb-raider-game-of-the-year-6-hitman-absolution-elite-edition") |
|
144 |
+ expect(last.payload['feed']).to include({ |
|
145 |
+ "type" => "rss", |
|
146 |
+ "title" => "SlickDeals.net", |
|
147 |
+ "description" => "Slick online shopping deals.", |
|
148 |
+ "url" => "http://slickdeals.net/", |
|
149 |
+ }) |
|
150 |
+ expect(last.payload['url']).to eq("http://slickdeals.net/permadeal/129980/amazon---rearth-ringke-fusion-bumper-hybrid-case-for-iphone-6") |
|
151 |
+ end |
|
152 |
+ |
|
85 | 153 |
it "should track ids and not re-emit the same item when seen again" do |
86 | 154 |
agent.check |
87 | 155 |
expect(agent.memory['seen_ids']).to eq(agent.events.map {|e| e.payload['id'] }) |
@@ -155,17 +223,39 @@ describe Agents::RssAgent do |
||
155 | 223 |
@valid_options['url'] = 'http://onethingwell.org/rss' |
156 | 224 |
end |
157 | 225 |
|
226 |
+ it "captures timestamps normalized in the ISO 8601 format" do |
|
227 |
+ agent.check |
|
228 |
+ first, *, third = agent.events.take(3) |
|
229 |
+ expect(first.payload['date_published']).to eq('2015-08-20T17:00:10+01:00') |
|
230 |
+ expect(third.payload['date_published']).to eq('2015-08-20T13:00:07+01:00') |
|
231 |
+ end |
|
232 |
+ |
|
158 | 233 |
it "captures multiple categories" do |
159 | 234 |
agent.check |
160 | 235 |
first, *, third = agent.events.take(3) |
161 | 236 |
expect(first.payload['categories']).to eq(["csv", "crossplatform", "utilities"]) |
162 | 237 |
expect(third.payload['categories']).to eq(["web"]) |
163 | 238 |
end |
239 |
+ |
|
240 |
+ it "sanitizes HTML content" do |
|
241 |
+ agent.options['clean'] = true |
|
242 |
+ agent.check |
|
243 |
+ event = agent.events.last |
|
244 |
+ expect(event.payload['content']).to eq('<a href="http://showgoers.tv/">Showgoers</a>: <blockquote> <p>Showgoers is a Chrome browser extension to synchronize your Netflix player with someone else so that you can co-watch the same movie on different computers with no hassle. Syncing up your player is as easy as sharing a URL.</p> </blockquote>') |
|
245 |
+ expect(event.payload['description']).to eq('<a href="http://showgoers.tv/">Showgoers</a>: <blockquote> <p>Showgoers is a Chrome browser extension to synchronize your Netflix player with someone else so that you can co-watch the same movie on different computers with no hassle. Syncing up your player is as easy as sharing a URL.</p> </blockquote>') |
|
246 |
+ end |
|
247 |
+ |
|
248 |
+ it "captures an enclosure" do |
|
249 |
+ agent.check |
|
250 |
+ event = agent.events.fourth |
|
251 |
+ expect(event.payload['enclosure']).to eq({ "url" => "http://c.1tw.org/images/2015/itsy.png", "type" => "image/png", "length" => "48249" }) |
|
252 |
+ expect(event.payload['image']).to eq("http://c.1tw.org/images/2015/itsy.png") |
|
253 |
+ end |
|
164 | 254 |
end |
165 | 255 |
|
166 | 256 |
describe 'logging errors with the feed url' do |
167 | 257 |
it 'includes the feed URL when an exception is raised' do |
168 |
- mock(FeedNormalizer::FeedNormalizer).parse(anything, loose: true) { raise StandardError.new("Some error!") } |
|
258 |
+ mock(Feedjira::Feed).parse(anything) { raise StandardError.new("Some error!") } |
|
169 | 259 |
expect(lambda { |
170 | 260 |
agent.check |
171 | 261 |
}).not_to raise_error |
@@ -24,11 +24,11 @@ describe Agents::TwitterActionAgent do |
||
24 | 24 |
|
25 | 25 |
context 'when set up to retweet' do |
26 | 26 |
before do |
27 |
- @agent = build_agent({ |
|
28 |
- 'expected_receive_period_in_days' => '2', |
|
27 |
+ @agent = build_agent( |
|
29 | 28 |
'favorite' => 'false', |
30 | 29 |
'retweet' => 'true', |
31 |
- }) |
|
30 |
+ 'emit_error_events' => 'true' |
|
31 |
+ ) |
|
32 | 32 |
@agent.save! |
33 | 33 |
end |
34 | 34 |
|
@@ -68,9 +68,9 @@ describe Agents::TwitterActionAgent do |
||
68 | 68 |
context 'when set up to favorite' do |
69 | 69 |
before do |
70 | 70 |
@agent = build_agent( |
71 |
- 'expected_receive_period_in_days' => '2', |
|
72 | 71 |
'favorite' => 'true', |
73 | 72 |
'retweet' => 'false', |
73 |
+ 'emit_error_events' => 'true' |
|
74 | 74 |
) |
75 | 75 |
@agent.save! |
76 | 76 |
end |
@@ -107,13 +107,48 @@ describe Agents::TwitterActionAgent do |
||
107 | 107 |
end |
108 | 108 |
end |
109 | 109 |
end |
110 |
+ |
|
111 |
+ context 'with emit_error_events set to false' do |
|
112 |
+ it 'does re-raises the exception on failure' do |
|
113 |
+ agent = build_agent |
|
114 |
+ |
|
115 |
+ stub(agent.twitter).retweet(anything) { |
|
116 |
+ raise Twitter::Error.new('uh oh') |
|
117 |
+ } |
|
118 |
+ |
|
119 |
+ expect { agent.receive([@event1]) }.to raise_error(StandardError, /uh oh/) |
|
120 |
+ |
|
121 |
+ end |
|
122 |
+ end |
|
110 | 123 |
end |
111 | 124 |
|
112 | 125 |
describe "#validate_options" do |
126 |
+ it 'the default options are valid' do |
|
127 |
+ agent = build_agent(described_class.new.default_options) |
|
128 |
+ |
|
129 |
+ expect(agent).to be_valid |
|
130 |
+ end |
|
131 |
+ |
|
132 |
+ context 'emit_error_events' do |
|
133 |
+ it 'can be set to true' do |
|
134 |
+ agent = build_agent(described_class.new.default_options.merge('emit_error_events' => 'true')) |
|
135 |
+ expect(agent).to be_valid |
|
136 |
+ end |
|
137 |
+ |
|
138 |
+ it 'must be a boolean' do |
|
139 |
+ agent = build_agent(described_class.new.default_options.merge('emit_error_events' => 'notbolean')) |
|
140 |
+ expect(agent).not_to be_valid |
|
141 |
+ end |
|
142 |
+ end |
|
143 |
+ |
|
144 |
+ it 'expected_receive_period_in_days must be set' do |
|
145 |
+ agent = build_agent(described_class.new.default_options.merge('expected_receive_period_in_days' => '')) |
|
146 |
+ expect(agent).not_to be_valid |
|
147 |
+ end |
|
148 |
+ |
|
113 | 149 |
context 'when set up to neither favorite or retweet' do |
114 | 150 |
it 'is invalid' do |
115 | 151 |
agent = build_agent( |
116 |
- 'expected_receive_period_in_days' => '2', |
|
117 | 152 |
'favorite' => 'false', |
118 | 153 |
'retweet' => 'false', |
119 | 154 |
) |
@@ -129,11 +164,7 @@ describe Agents::TwitterActionAgent do |
||
129 | 164 |
end |
130 | 165 |
|
131 | 166 |
it 'checks if events have been received within the expected time period' do |
132 |
- agent = build_agent( |
|
133 |
- 'expected_receive_period_in_days' => '2', |
|
134 |
- 'favorite' => 'false', |
|
135 |
- 'retweet' => 'true', |
|
136 |
- ) |
|
167 |
+ agent = build_agent |
|
137 | 168 |
agent.save! |
138 | 169 |
|
139 | 170 |
expect(agent).not_to be_working # No events received |
@@ -147,10 +178,10 @@ describe Agents::TwitterActionAgent do |
||
147 | 178 |
end |
148 | 179 |
end |
149 | 180 |
|
150 |
- def build_agent(options) |
|
181 |
+ def build_agent(options = {}) |
|
151 | 182 |
described_class.new do |agent| |
152 | 183 |
agent.name = 'twitter stuff' |
153 |
- agent.options = options |
|
184 |
+ agent.options = agent.default_options.merge(options) |
|
154 | 185 |
agent.service = services(:generic) |
155 | 186 |
agent.user = users(:bob) |
156 | 187 |
end |
@@ -19,6 +19,16 @@ describe Agents::WeatherAgent do |
||
19 | 19 |
it "creates a valid agent" do |
20 | 20 |
expect(agent).to be_valid |
21 | 21 |
end |
22 |
+ |
|
23 |
+ it "is valid with put-your-key-here or your-key" do |
|
24 |
+ agent.options['api_key'] = 'put-your-key-here' |
|
25 |
+ expect(agent).to be_valid |
|
26 |
+ expect(agent.working?).to be_falsey |
|
27 |
+ |
|
28 |
+ agent.options['api_key'] = 'your-key' |
|
29 |
+ expect(agent).to be_valid |
|
30 |
+ expect(agent.working?).to be_falsey |
|
31 |
+ end |
|
22 | 32 |
|
23 | 33 |
describe "#service" do |
24 | 34 |
it "doesn't have a Service object attached" do |
@@ -49,6 +49,12 @@ describe Agents::WebhookAgent do |
||
49 | 49 |
expect(out).to eq(['', 201]) |
50 | 50 |
end |
51 | 51 |
|
52 |
+ it 'should respond with interpolated response message if configured with `response` option' do |
|
53 |
+ agent.options['response'] = '{{some_key.people[1].name}}' |
|
54 |
+ out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html") |
|
55 |
+ expect(out).to eq(['jon', 201]) |
|
56 |
+ end |
|
57 |
+ |
|
52 | 58 |
it 'should respond with `Event Created` if the response option is nil or missing' do |
53 | 59 |
agent.options['response'] = nil |
54 | 60 |
out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html") |
@@ -59,6 +65,26 @@ describe Agents::WebhookAgent do |
||
59 | 65 |
expect(out).to eq(['Event Created', 201]) |
60 | 66 |
end |
61 | 67 |
|
68 |
+ it 'should respond with customized response code if configured with `code` option' do |
|
69 |
+ agent.options['code'] = '200' |
|
70 |
+ out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html") |
|
71 |
+ expect(out).to eq(['Event Created', 200]) |
|
72 |
+ end |
|
73 |
+ |
|
74 |
+ it 'should respond with `201` if the code option is empty, nil or missing' do |
|
75 |
+ agent.options['code'] = '' |
|
76 |
+ out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html") |
|
77 |
+ expect(out).to eq(['Event Created', 201]) |
|
78 |
+ |
|
79 |
+ agent.options['code'] = nil |
|
80 |
+ out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html") |
|
81 |
+ expect(out).to eq(['Event Created', 201]) |
|
82 |
+ |
|
83 |
+ agent.options.delete('code') |
|
84 |
+ out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html") |
|
85 |
+ expect(out).to eq(['Event Created', 201]) |
|
86 |
+ end |
|
87 |
+ |
|
62 | 88 |
describe "receiving events" do |
63 | 89 |
|
64 | 90 |
context "default settings" do |
@@ -1105,8 +1105,8 @@ fire: hot |
||
1105 | 1105 |
|
1106 | 1106 |
describe "#check" do |
1107 | 1107 |
before do |
1108 |
- expect { @checker.check }.to change { Event.count }.by(7) |
|
1109 |
- @events = Event.last(7) |
|
1108 |
+ expect { @checker.check }.to change { Event.count }.by(8) |
|
1109 |
+ @events = Event.last(8) |
|
1110 | 1110 |
end |
1111 | 1111 |
|
1112 | 1112 |
it "should check hostname" do |
@@ -1143,6 +1143,11 @@ fire: hot |
||
1143 | 1143 |
event = @events[6] |
1144 | 1144 |
expect(event.payload['url']).to eq("https://www.google.ca/search?q=%EC%9C%84%ED%82%A4%EB%B0%B1%EA%B3%BC:%EB%8C%80%EB%AC%B8") |
1145 | 1145 |
end |
1146 |
+ |
|
1147 |
+ it "should check url with unescaped brackets in the path component" do |
|
1148 |
+ event = @events[7] |
|
1149 |
+ expect(event.payload['url']).to eq("http://[::1]/path%5B%5D?query[]=foo") |
|
1150 |
+ end |
|
1146 | 1151 |
end |
1147 | 1152 |
end |
1148 | 1153 |
end |
@@ -98,6 +98,7 @@ describe Service do |
||
98 | 98 |
expect(service.token).to eq('a1b2c3d4...') |
99 | 99 |
expect(service.secret).to eq('abcdef1234') |
100 | 100 |
end |
101 |
+ |
|
101 | 102 |
it "should work with 37signals services" do |
102 | 103 |
signals = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/37signals.json'))) |
103 | 104 |
expect { |
@@ -113,6 +114,7 @@ describe Service do |
||
113 | 114 |
expect(service.options[:user_id]).to eq(12345) |
114 | 115 |
service.expires_at = Time.at(1401554352) |
115 | 116 |
end |
117 |
+ |
|
116 | 118 |
it "should work with github services" do |
117 | 119 |
signals = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/github.json'))) |
118 | 120 |
expect { |
@@ -126,4 +128,27 @@ describe Service do |
||
126 | 128 |
expect(service.token).to eq('agithubtoken') |
127 | 129 |
end |
128 | 130 |
end |
131 |
+ |
|
132 |
+ describe 'omniauth options provider registry for non-conforming omniauth responses' do |
|
133 |
+ describe '.register_options_provider' do |
|
134 |
+ before do |
|
135 |
+ Service.register_options_provider('test-omniauth-provider') do |omniauth| |
|
136 |
+ { name: omniauth['special_field'] } |
|
137 |
+ end |
|
138 |
+ end |
|
139 |
+ |
|
140 |
+ after do |
|
141 |
+ Service.option_providers.delete('test-omniauth-provider') |
|
142 |
+ end |
|
143 |
+ |
|
144 |
+ it 'allows gem developers to add their own options provider to the registry' do |
|
145 |
+ actual_options = Service.get_options({ |
|
146 |
+ 'provider' => 'test-omniauth-provider', |
|
147 |
+ 'special_field' => 'A Great Name' |
|
148 |
+ }) |
|
149 |
+ |
|
150 |
+ expect(actual_options[:name]).to eq('A Great Name') |
|
151 |
+ end |
|
152 |
+ end |
|
153 |
+ end |
|
129 | 154 |
end |
@@ -8,14 +8,6 @@ describe UserCredential do |
||
8 | 8 |
it { should validate_presence_of(:user_id) } |
9 | 9 |
end |
10 | 10 |
|
11 |
- describe "mass assignment" do |
|
12 |
- it { should allow_mass_assignment_of :credential_name } |
|
13 |
- |
|
14 |
- it { should allow_mass_assignment_of :credential_value } |
|
15 |
- |
|
16 |
- it { should_not allow_mass_assignment_of :user_id } |
|
17 |
- end |
|
18 |
- |
|
19 | 11 |
describe "cleaning fields" do |
20 | 12 |
it "should trim whitespace" do |
21 | 13 |
user_credential = user_credentials(:bob_aws_key) |
@@ -1,6 +1,8 @@ |
||
1 | 1 |
require 'rails_helper' |
2 | 2 |
|
3 | 3 |
describe User do |
4 |
+ let(:bob) { users(:bob) } |
|
5 |
+ |
|
4 | 6 |
describe "validations" do |
5 | 7 |
describe "invitation_code" do |
6 | 8 |
context "when configured to use invitation codes" do |
@@ -64,4 +66,29 @@ describe User do |
||
64 | 66 |
expect(users(:bob).deactivated_at).to be_nil |
65 | 67 |
end |
66 | 68 |
end |
69 |
+ |
|
70 |
+ context '#undefined_agent_types' do |
|
71 |
+ it 'returns an empty array when no agents are undefined' do |
|
72 |
+ expect(bob.undefined_agent_types).to be_empty |
|
73 |
+ end |
|
74 |
+ |
|
75 |
+ it 'returns the undefined agent types' do |
|
76 |
+ agent = agents(:bob_website_agent) |
|
77 |
+ agent.update_attribute(:type, 'Agents::UndefinedAgent') |
|
78 |
+ expect(bob.undefined_agent_types).to match_array(['Agents::UndefinedAgent']) |
|
79 |
+ end |
|
80 |
+ end |
|
81 |
+ |
|
82 |
+ context '#undefined_agents' do |
|
83 |
+ it 'returns an empty array when no agents are undefined' do |
|
84 |
+ expect(bob.undefined_agents).to be_empty |
|
85 |
+ end |
|
86 |
+ |
|
87 |
+ it 'returns the undefined agent types' do |
|
88 |
+ agent = agents(:bob_website_agent) |
|
89 |
+ agent.update_attribute(:type, 'Agents::UndefinedAgent') |
|
90 |
+ expect(bob.undefined_agents).not_to be_empty |
|
91 |
+ expect(bob.undefined_agents.first).to be_a(Agent) |
|
92 |
+ end |
|
93 |
+ end |
|
67 | 94 |
end |
@@ -13,7 +13,7 @@ require 'rspec/rails' |
||
13 | 13 |
require 'rr' |
14 | 14 |
require 'webmock/rspec' |
15 | 15 |
|
16 |
-WebMock.disable_net_connect! |
|
16 |
+WebMock.disable_net_connect!(allow_localhost: true) |
|
17 | 17 |
|
18 | 18 |
# Requires supporting ruby files with custom matchers and macros, etc, |
19 | 19 |
# in spec/support/ and its subdirectories. |
@@ -66,7 +66,7 @@ RSpec.configure do |config| |
||
66 | 66 |
|
67 | 67 |
config.render_views |
68 | 68 |
|
69 |
- config.include Devise::TestHelpers, type: :controller |
|
69 |
+ config.include Devise::Test::ControllerHelpers, type: :controller |
|
70 | 70 |
config.include SpecHelpers |
71 | 71 |
config.include Delorean |
72 | 72 |
end |
@@ -1,6 +1,8 @@ |
||
1 | 1 |
require 'rails_helper' |
2 | 2 |
|
3 | 3 |
shared_examples_for 'FileHandlingConsumer' do |
4 |
+ let(:event) { Event.new(user: @checker.user, payload: {'file_pointer' => {'file' => 'text.txt', 'agent_id' => @checker.id}}) } |
|
5 |
+ |
|
4 | 6 |
it 'returns a file pointer' do |
5 | 7 |
expect(@checker.get_file_pointer('testfile')).to eq(file_pointer: { file: "testfile", agent_id: @checker.id}) |
6 | 8 |
end |
@@ -9,8 +11,26 @@ shared_examples_for 'FileHandlingConsumer' do |
||
9 | 11 |
@checker2 = @checker.dup |
10 | 12 |
@checker2.user = users(:bob) |
11 | 13 |
@checker2.save! |
12 |
- expect(@checker2.user.id).not_to eq(@checker.user.id) |
|
13 |
- event = Event.new(user: @checker.user, payload: {'file_pointer' => {'file' => 'test', 'agent_id' => @checker2.id}}) |
|
14 |
+ event.payload['file_pointer']['agent_id'] = @checker2.id |
|
14 | 15 |
expect { @checker.get_io(event) }.to raise_error(ActiveRecord::RecordNotFound) |
15 | 16 |
end |
16 |
-end |
|
17 |
+ |
|
18 |
+ context '#has_file_pointer?' do |
|
19 |
+ it 'returns true if the event contains a file pointer' do |
|
20 |
+ expect(@checker.has_file_pointer?(event)).to be_truthy |
|
21 |
+ end |
|
22 |
+ |
|
23 |
+ it 'returns false if the event does not contain a file pointer' do |
|
24 |
+ expect(@checker.has_file_pointer?(Event.new)).to be_falsy |
|
25 |
+ end |
|
26 |
+ end |
|
27 |
+ |
|
28 |
+ it '#get_upload_io returns a Faraday::UploadIO instance' do |
|
29 |
+ io_mock = mock() |
|
30 |
+ mock(@checker).get_io(event) { StringIO.new("testdata") } |
|
31 |
+ |
|
32 |
+ upload_io = @checker.get_upload_io(event) |
|
33 |
+ expect(upload_io).to be_a(Faraday::UploadIO) |
|
34 |
+ expect(upload_io.content_type).to eq('text/plain') |
|
35 |
+ end |
|
36 |
+end |
@@ -1,40 +1,146 @@ |
||
1 |
-// |
|
2 |
-// Use internal $.serializeArray to get list of form elements which is |
|
3 |
-// consistent with $.serialize |
|
4 |
-// |
|
5 |
-// From version 2.0.0, $.serializeObject will stop converting [name] values |
|
6 |
-// to camelCase format. This is *consistent* with other serialize methods: |
|
7 |
-// |
|
8 |
-// - $.serialize |
|
9 |
-// - $.serializeArray |
|
10 |
-// |
|
11 |
-// If you require camel casing, you can either download version 1.0.4 or map |
|
12 |
-// them yourself. |
|
13 |
-// |
|
14 |
- |
|
15 |
-(function($){ |
|
16 |
- $.fn.serializeObject = function () { |
|
17 |
- "use strict"; |
|
18 |
- |
|
19 |
- var result = {}; |
|
20 |
- var extend = function (i, element) { |
|
21 |
- var node = result[element.name]; |
|
22 |
- |
|
23 |
- // If node with same name exists already, need to convert it to an array as it |
|
24 |
- // is a multi-value field (i.e., checkboxes) |
|
25 |
- |
|
26 |
- if ('undefined' !== typeof node && node !== null) { |
|
27 |
- if ($.isArray(node)) { |
|
28 |
- node.push(element.value); |
|
29 |
- } else { |
|
30 |
- result[element.name] = [node, element.value]; |
|
1 |
+/** |
|
2 |
+ * jQuery serializeObject |
|
3 |
+ * @copyright 2014, macek <paulmacek@gmail.com> |
|
4 |
+ * @link https://github.com/macek/jquery-serialize-object |
|
5 |
+ * @license BSD |
|
6 |
+ * @version 2.5.0 |
|
7 |
+ */ |
|
8 |
+(function(root, factory) { |
|
9 |
+ |
|
10 |
+ // AMD |
|
11 |
+ if (typeof define === "function" && define.amd) { |
|
12 |
+ define(["exports", "jquery"], function(exports, $) { |
|
13 |
+ return factory(exports, $); |
|
14 |
+ }); |
|
15 |
+ } |
|
16 |
+ |
|
17 |
+ // CommonJS |
|
18 |
+ else if (typeof exports !== "undefined") { |
|
19 |
+ var $ = require("jquery"); |
|
20 |
+ factory(exports, $); |
|
21 |
+ } |
|
22 |
+ |
|
23 |
+ // Browser |
|
24 |
+ else { |
|
25 |
+ factory(root, (root.jQuery || root.Zepto || root.ender || root.$)); |
|
26 |
+ } |
|
27 |
+ |
|
28 |
+}(this, function(exports, $) { |
|
29 |
+ |
|
30 |
+ var patterns = { |
|
31 |
+ validate: /^[a-z_][a-z0-9_]*(?:\[(?:\d*|[a-z0-9_]+)\])*$/i, |
|
32 |
+ key: /[a-z0-9_]+|(?=\[\])/gi, |
|
33 |
+ push: /^$/, |
|
34 |
+ fixed: /^\d+$/, |
|
35 |
+ named: /^[a-z0-9_]+$/i |
|
36 |
+ }; |
|
37 |
+ |
|
38 |
+ function FormSerializer(helper, $form) { |
|
39 |
+ |
|
40 |
+ // private variables |
|
41 |
+ var data = {}, |
|
42 |
+ pushes = {}; |
|
43 |
+ |
|
44 |
+ // private API |
|
45 |
+ function build(base, key, value) { |
|
46 |
+ base[key] = value; |
|
47 |
+ return base; |
|
48 |
+ } |
|
49 |
+ |
|
50 |
+ function makeObject(root, value) { |
|
51 |
+ |
|
52 |
+ var keys = root.match(patterns.key), k; |
|
53 |
+ |
|
54 |
+ // nest, nest, ..., nest |
|
55 |
+ while ((k = keys.pop()) !== undefined) { |
|
56 |
+ // foo[] |
|
57 |
+ if (patterns.push.test(k)) { |
|
58 |
+ var idx = incrementPush(root.replace(/\[\]$/, '')); |
|
59 |
+ value = build([], idx, value); |
|
60 |
+ } |
|
61 |
+ |
|
62 |
+ // foo[n] |
|
63 |
+ else if (patterns.fixed.test(k)) { |
|
64 |
+ value = build([], k, value); |
|
65 |
+ } |
|
66 |
+ |
|
67 |
+ // foo; foo[bar] |
|
68 |
+ else if (patterns.named.test(k)) { |
|
69 |
+ value = build({}, k, value); |
|
31 | 70 |
} |
32 |
- } else { |
|
33 |
- result[element.name] = element.value; |
|
34 | 71 |
} |
35 |
- }; |
|
36 | 72 |
|
37 |
- $.each(this.serializeArray(), extend); |
|
38 |
- return result; |
|
73 |
+ return value; |
|
74 |
+ } |
|
75 |
+ |
|
76 |
+ function incrementPush(key) { |
|
77 |
+ if (pushes[key] === undefined) { |
|
78 |
+ pushes[key] = 0; |
|
79 |
+ } |
|
80 |
+ return pushes[key]++; |
|
81 |
+ } |
|
82 |
+ |
|
83 |
+ function encode(pair) { |
|
84 |
+ switch ($('[name="' + pair.name + '"]', $form).attr("type")) { |
|
85 |
+ case "checkbox": |
|
86 |
+ return pair.value === "on" ? true : pair.value; |
|
87 |
+ default: |
|
88 |
+ return pair.value; |
|
89 |
+ } |
|
90 |
+ } |
|
91 |
+ |
|
92 |
+ function addPair(pair) { |
|
93 |
+ if (!patterns.validate.test(pair.name)) return this; |
|
94 |
+ var obj = makeObject(pair.name, encode(pair)); |
|
95 |
+ data = helper.extend(true, data, obj); |
|
96 |
+ return this; |
|
97 |
+ } |
|
98 |
+ |
|
99 |
+ function addPairs(pairs) { |
|
100 |
+ if (!helper.isArray(pairs)) { |
|
101 |
+ throw new Error("formSerializer.addPairs expects an Array"); |
|
102 |
+ } |
|
103 |
+ for (var i=0, len=pairs.length; i<len; i++) { |
|
104 |
+ this.addPair(pairs[i]); |
|
105 |
+ } |
|
106 |
+ return this; |
|
107 |
+ } |
|
108 |
+ |
|
109 |
+ function serialize() { |
|
110 |
+ return data; |
|
111 |
+ } |
|
112 |
+ |
|
113 |
+ function serializeJSON() { |
|
114 |
+ return JSON.stringify(serialize()); |
|
115 |
+ } |
|
116 |
+ |
|
117 |
+ // public API |
|
118 |
+ this.addPair = addPair; |
|
119 |
+ this.addPairs = addPairs; |
|
120 |
+ this.serialize = serialize; |
|
121 |
+ this.serializeJSON = serializeJSON; |
|
122 |
+ } |
|
123 |
+ |
|
124 |
+ FormSerializer.patterns = patterns; |
|
125 |
+ |
|
126 |
+ FormSerializer.serializeObject = function serializeObject() { |
|
127 |
+ return new FormSerializer($, this). |
|
128 |
+ addPairs(this.serializeArray()). |
|
129 |
+ serialize(); |
|
130 |
+ }; |
|
131 |
+ |
|
132 |
+ FormSerializer.serializeJSON = function serializeJSON() { |
|
133 |
+ return new FormSerializer($, this). |
|
134 |
+ addPairs(this.serializeArray()). |
|
135 |
+ serializeJSON(); |
|
39 | 136 |
}; |
40 |
-})(jQuery); |
|
137 |
+ |
|
138 |
+ if (typeof $.fn !== "undefined") { |
|
139 |
+ $.fn.serializeObject = FormSerializer.serializeObject; |
|
140 |
+ $.fn.serializeJSON = FormSerializer.serializeJSON; |
|
141 |
+ } |
|
142 |
+ |
|
143 |
+ exports.FormSerializer = FormSerializer; |
|
144 |
+ |
|
145 |
+ return FormSerializer; |
|
146 |
+})); |